sdj extra 34 biblia

220

Upload: damian-ogorow

Post on 21-Jun-2015

1.240 views

Category:

Documents


3 download

DESCRIPTION

Software Developers Jurnal

TRANSCRIPT

Page 1: SDJ Extra 34 Biblia
Page 2: SDJ Extra 34 Biblia

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

Page 3: SDJ Extra 34 Biblia
Page 4: SDJ Extra 34 Biblia

4

OD REDAKCJI

Otaczają 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ńskiRedaktor naczelny

Rafał KociszRedaktor prowadzący

4 SDJ Extra 34 Biblia

Page 5: SDJ Extra 34 Biblia
Page 6: SDJ Extra 34 Biblia

6 SDJ Extra 34 Biblia 7www.sdjournal.org

SDJ Extra 34 Biblia

SPIS TREŚCI 10 Opis DVD

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

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

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

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

56 Moc pod kontrolą – Efektywne zarządzanie zasi-laniem w aplikacjach dla systemu Windows MobileMarian WitkowskiOdwiecznym problemem i wyzwaniem dla konstruktorów urzą-dzeń mobilnych była możliwość ich długiej pracy z wykorzysta-

6

Page 7: SDJ Extra 34 Biblia

6 SDJ Extra 34 Biblia 7www.sdjournal.org

niem zasilania bateryjnego. Cel ten został w dużej mierze osią-gnięty, ale mimo wszystko źle napisana aplikacja potrafi zmusić użytkownika do częstszego używania ładowarki.

PROGRAMOWANIE SYMBIAN OS62 Obsługa akcelerometru na platformie Series60 – Wykorzystaj w pełni możliwości telefonu komórkowego!Rafał Kocisz, Wojciech GasekAkcelerometr, bądź inaczej – sensor ruchu, od stosunkowo niedaw-na jest wykorzystywany jako rozszerzenie technologicznych możli-wości nowoczesnych telefonów komórkowych. Artykuł pokazuje, jak oprogramować akcelerometr w aplikacjach Symbian OS, działa-jących na platformie S60, oraz jak w praktyce wykorzystać potencjał tego urządzenia.

78 SDL na Symbianie – Cztery portyBartosz TaudulW 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-nia? Odpowiedzi na wszystkie te pytania (i nie tylko) znajdu-ją się w artykule.

PROGRAMOWANIE ANDROID90 Android Market bliżej dewelopera – Stworze-nie aplikacji to dopiero początek. Dowiedz się, jak ją sprzedać!Adam SkrzyszewskiGłównym zmartwieniem deweloperów planujących sprzedaż swoich programów jest dotarcie do odpowiedniej grupy od-biorców. Android Market, jako oficjalna platforma dystrybucyj-na, zakłada nieograniczony dostęp do wszystkich posiadaczy urządzeń z oprogramowaniem Google. Czy zastanawiałeś się, jak wykorzystać ten ogromny potencjał?

98 Android na przykładzie: geotagging zdjęć – Praktyczne wykorzystanie najciekawszych funk-cji mobilnej platformy firmy GooglePiotr BułaAndroid to nowa, otwarta platforma mobilna od Google. W artykule przedstawiono ciekawsze jej możliwości, na przy-kładzie aplikacji umożliwiającej geotagging zdjęć wykona-nych za pomocą telefonu komórkowego opartego na tej plat-formie.

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

Dyrektor wydawniczy: Anna Adamczyk

Redaktor naczelny: Łukasz Łopuszański [email protected]

Redaktor prowadzący: Rafał Kocisz [email protected]

Korekta: Tomasz Łopuszański

Projekt okładki: Agnieszka MarchockaMonika Grotkowska

Skład i łamanie: Monika Grotkowska [email protected]

Tomasz Kostro [email protected]

Dział produkcji i kolportażu: Alina Stebakow [email protected]

Kierownik produkcji: Andrzej Kuca [email protected]

Nakład: 6 000 egz.

Adres korespondencyjny:Software Press Sp. z o.o. SK,

ul. Bokserska 1, 02-682 Warszawa, Polskatel. +48 22 427 36 91, fax +48 22 224 24 59

www.sdjournal.org [email protected]

Dział reklamy: [email protected]

Obsługa prenumeraty: EuroPress Polska [email protected]

Dołączoną do magazynu płytę DVD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware,

freeware i public domain.Uszkodzone podczas wysyłki płyty wymienia redakcja.

Wszystkie znaki firmowe zawarte w piśmie są własności odpowiednich firm.

Zostały użyte wyłącznie w celach informacyjnych.

Redakcja używa systemu automatycznego składu

Osoby zainteresowane współpracą prosimy o kontakt:[email protected]

Druk: ArtDruk www.artdruk.com

Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu programów

zamieszczonych na płycie DVD dostarczonej razem z pismem.

Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest

działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.

Page 8: SDJ Extra 34 Biblia

8 SDJ Extra 34 Biblia 9www.sdjournal.org

118 Google Android – Programowanie z wykorzy-staniem Google Maps Igor KrukW artykule „JME – MIDlet – Przelicznik walut na podstawie kur-sów NBP” opisuje architekturę JME oraz prezentuje sposób, w ja-ki można rozpocząć przygodę z programowaniem z wykorzysta-niem tej technologii. Artykuł poświęcony jest platformie Android, na której można tworzyć zaawansowane aplikacje m.in. z wyko-rzystaniem Google Maps.

124 Google Android – Programowanie interfejsu użytkownika pod Android OSTomasz MilczarekArtykuł jest wprowadzeniem do programowania interfejsu użyt-kownika (UI – User Interface) na platformie Google Android. Oma-wia podstawowe komponenty interfejsu i sposoby rozmieszcza-nia ich na ekranie. Przedstawia również tworzenie formularzy, przenoszenie danych między formularzami oraz interakcję mię-dzy różnymi ekranami użytkownika.

PROGRAMOWANIE IPHONE OS132 Poznajemy iPhone SDK – Pierwsze krokiTomasz DubkiNikt nie zaprzeczy, że jednym z najpopularniejszych urządzeń mo-bilnych ostatnich 12 miesięcy jest iPhone. Doskonały wygląd ze-wnętrzny, przejrzysty graficzny interfejs użytkownika oraz bogata funkcjonalność to tylko niektóre jego cechy. W niniejszym artykule przedstawimy to urządzenie z punktu widzenia programisty.

140 Objective-C kontra Java i C++ – Wprowadze-nie do językaRafał KociszJava 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 Objecti-ve-C oraz porównanie jego możliwoci z językami Java i C++.

PROGRAMOWANIE JAVAME148 JME na przykładzie – Przelicznik walut na podstawie kursów NBPIgor KrukJeszcze 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 tech-nologia JME jest nadal popularna.

154 Klasyczna platformówka na komórkę – Pisze-my grę bazującą na platformie Java Micro EditionCezariusz KlonkowskiTworzenie każdej gry, nawet najprostszej, łączy w sobie wiele wy-zwań, z którymi musi uporać się programista. Tworzenie aplika-cji dla platformy mobilnej niesie dodatkowe problemy. Jednak sa-tysfakcja z napisania własnej, nawet bardzo prostej gry na telefon komórkowy, jest bezcenna! Jeśli chciałbyś poczuć taką satysfak-cję, to przeczytaj ten artykuł.

168 Testowanie aplikacji na platformie JME – Zbieranie informacji debugowych w ograniczo-nym środowisku uruchomieniowymMarcin Maciejewski

ROZWIĄZANIA MOBILNE172 Lotus Notes TravelerAndrzej OlsztyńskiW tym roku, zgodnie z danymi IBM, po raz pierwszy w historii, więcej ludzi na świecie będzie miało telefon komórkowy niż sta-cjonarny. Wiele produktów IBM jest dostępnych na platformach mobilnych. Ostatnim ważnym wydarzeniem w tym zakresie by-ło wprowadzenie oprogramowania Lotus Notes Domino, czyli pa-kietu IBM do pracy grupowej na iPhone.

SDJ Extra 34 Biblia

Page 9: SDJ Extra 34 Biblia

8 SDJ Extra 34 Biblia 9www.sdjournal.org

178 Stary software – nowa platformaKamil Kowalski, Artur ChruścielW jednym z numerów SDJ (2/2009) przybliżyliśmy profil prac ze-społu zajmującego się rozwojem oprogramowania dla radiote-lefonów systemu TETRA w krakowskim centrum Motoroli. Dziś przyjrzymy się problemom, przed którymi stanęliśmy w ostatnich latach w związku z migracją na nowe platformy sprzętowe.

182 Oracle SPATIAL – Opis podstawowych funk-cjonalności opcji Oracle SPATIAL dedykowanej dla systemów GISTomasz MurtaśPoprzez opcję SPATIAL baza danych zyskuje możliwość zarządza-nia danymi o charakterze przestrzennym. To zupełnie nowy wy-miar pozwalający jeszcze precyzyjniej odwzorowywać rzeczywi-stość, modelować dane i analizować zależności.

186 Wzorce projektowe w Java CardLeszek Siwik, Krzysztof Lewandowski, Adam WośTworzenie oprogramowania wymaga starannego projektu i du-żej dokładności. Platforma Java Card do tej pory pozostawała swego rodzaju skansenem, gdzie szeroko akceptowany był kod, który w innych obszarach byłby absolutnie nieakceptowalny. Z pewnością warto więc poświęcić czas na zapoznanie się z do-brymi praktykami projektowania i programowania

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

206 Flash LiteGrzegorz Trubiłowicz, Mateusz Pietrek, Karol BednarzFlash 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ć.

FELIETONY214 Raport większościArkadiusz MertaTechnologie mobilne otwierają nowe możliwości – być może jesz-cze bardziej rewolucyjne niż zjawisko Internetu. W odróżnieniu od Sieci – tort jest dzielony na innych zasadach, i to przez znacz-nie większą ilość graczy.

217 Ruch jest wszystkim [cel niczym]Arkadiusz MertaCo napędza rozwój technologi mobilnych? Czy to nasze potrzeby, czy potrzeba zwiększenia naszej efektywności.

Page 10: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia10 www.sdjournal.org 11

Opis CD

Kursy

WINDOWS MOBILEKurs podstaw tworzenia aplikacji na Windows Mobile (kurs w języku angielskim). Szkolenie zostało podzielone na 7 działów:

• wprowadzenie• emulatory• podstawy programowania aplikacji okienkowych• zaawansowane aplikacje Windows Forms• wykorzystanie baz danych• bezpieczeństwo • programowanie mobilnych aplikacji internetowych

W każdym z działów możemy wybrać jaki sposób podania tych samych zagadnień jest dla nas najwygodniejszy – doku-ment, laboratoria hands-on-lab, webcast z prezentacją czy scre-encast z kodem.

Mobile development is growing fast, and Windows Mobile is at the forefront with over 18 million phones shipped last year and many more cutting-edge devices on the way. Visual Stu-dio developers have tremendous opportunities in this space. Why? Developing for a Windows Mobile phone leverages your existing coding experience and takes it to new heights. In this track, we’ll go through the fundamentals of building mobile ap-plications. You’ll learn how to set up Visual Studio with the la-test SDK and device emulators, and you’ll see how to build, de-ploy and debug applications. We’ll also explore AJAX capabili-ties that offer the richness of the desktop for mobile devices.

Level 1: Mobile Development Introduction

Level 2: Device Emulators

Level 3: Mobile Windows Forms Development

Level 4: Advanced Mobile Windows Forms Development

Level 5: SQL Server CE Introduction

Level 6: Security and Deployment

Level 7: Mobile Web Development

Google Android – Programowanie interfejsu użytkownika pod

Android OSScreencast jest wprowadzeniem do programowania interfejsu użytkownika (UI – User Interface) na platformie Google Andro-id. Omawia podstawowe komponenty interfejsu i sposoby roz-mieszczania ich na ekranie. Przedstawia również tworzenie for-

mularzy, przenoszenie danych między formularzami oraz inte-rakcję między różnymi ekranami użytkownika

Google Android charakteryzuje się bogatym zestawem kom-ponentów pozwalającym na realizację nawet najbardziej skom-plikowanych interfejsów. W przypadku developerów niezbyt za-dowolonych ze standardowej biblioteki, zawsze pozostaje moż-liwość rozszerzania istniejących już komponentów.

Z racji tego, że Android tworzony jest przez firmę Google, ist-nieje łatwy dostęp do takich komponentów jak MapView czy WebView, umożliwiających korzystanie w tworzonych aplika-cjach z Google Maps oraz pozwalających na łatwe przeglądanie zawartości Internetu. To znacznie poszerza krąg pomysłów na aplikacje, już na starcie pozwala uczynić je bardziej ciekawymi i konkurencyjnymi.

Można sądzić, że powyższe zalety (prostota tworzenia inter-fejsów, duży wybór komponentów, łatwe wykorzystanie Google Maps i Internetu) spowodują, że Android OS umocni swoją po-zycję w świecie platform programistycznych na urządzenia mo-bilne.

Dzięki temu tutorialowi dowiesz się, jak poprawnie rozmie-ścić komponenty komponenty użytkownika na ekranie, jak tworzyć poszczególne widgety, jak ekrany użytkownika współ-pracują ze sobą.

Pierwsza część screencasta stanowi omówienie idei stojącej za powstaniem Google Android OS, podstawowych zasad, któ-re przyjęli twórcy tego systemu oraz przedstawiona jest jego ar-chitektura.

Omówione zostały typowe kontrolki i ich zastosowanie.Druga część screencasta przedstawia przykład tworzonej apli-

kacji – realizującej wykresy biorytmów. Tworzenie aplikacji zo-stało pokazane w następujących krokach:

• tworzenie nowego projektu w środowisku Eclipse ADT (Android Development Toolkit);

• konfiguracja projektu;• tworzenie podstawowej klasy akcji;• tworzenie layoutu;• definiowanie widoków w plikach XML;• oprogramowywanie kontrolek;• oprogramowywanie zdarzeń w Google Android;• interakcja pomiędzy kontrolkami a klasą typu Activity;• stworzenie przejścia między dwoma ekranami użytkownika;• przesyłanie danych między ekranami;• stworzenie własnego widoku.

NarzędziaWindows Mobile 6.5 Developer ToolkitWydana na początku czerwca 2009 dokumentacja, przykładowy kod, nagłówki, biblioteki oraz emulatory do najnowszej wersji syste-mu Windows Mobile 6.5.

Dostępne są dwie wersje – w zależności od tego czy interesu-je nas pisanie aplikacji dla systemu Windows Mobile Professional czy Standard.

Page 11: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia10 www.sdjournal.org 11

Opis CD

Toolkit wymaga instalacji Windows Mobile 6 SDK (do pobrania bezpłatnie ze stron Microsoftu).

AplikacjeConsumer Solution AcceleratorConsumer Solution Accelerator jest wzorcową aplikacją dla Win-dows Mobile napisaną w językach C# i C++, w oparciu o .NET Compact Framework 3.5.

Aplikacja ma na celu ułatwić użytkownikowi dotarcie na spotka-nie – prezentuje mapy wykorzystując nawigację GPS, szacuje czas

przejazdu, informuje uczestników spotkania w razie ewentualnego spóźnienia. Wraz z kodem aplikacji czytelnicy otrzymują zestaw do-kumentów opisujących krok po kroku poszczególne etapy tworze-nia danej aplikacji. Od wprowadzenia, przez dobór architektury aż po dodawanie do projektu kolejnych elementów kodu. Czytelnicy mogą sami stworzyć aplikacje na podstawie dokumentów lub uczyć się przez czytanie gotowego kodu.

Materiały dodatkowe do artykułów

Jeśli nie możesz odczytać zawartości płyty CD, a nie jest ona uszkodzona mechanicznie, sprawdź ją na co najmniej dwóch napędach CD. W razie problemów z płytą, prosimy pisać pod adres: [email protected]

Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu programów

zamieszczonych na płytach CD-ROM dostarczonych razem z pismem.

Page 12: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia12

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 13

Pamiętam jakby to było wczoraj: do-stałem do analizy kod źródłowy, na-leżało stwierdzić, czy flow progra-

mu jest zgodny z przyjętą koncepcją. Kod ani długi, ani krótki, zadanie wydawało się więc proste, i uznałem, że to nagroda za wcześniejszy okres wytężonej pracy... Kilka godzin i kaw, później niebezpiecznie blisko zbliżyłem się do granicy załamania nerwo-wego, a moje myśli skakały pomiędzy pla-nem zamachu na osobę, która wymyśliła, że wszystko dzieje się asynchronicznie, a rozważaniami na temat sposobów na skró-cenie swoich cierpień. Tak właśnie wyglą-dał mój pierwszy kontakt z RIL'em. Nieste-ty, a może właśnie stety od tamtego czasu nader często przyszło mi pracować w opar-ciu o wspomniane API. Swoimi doświad-czeniami postaram się podzielić w tym ar-

tykule, a ponieważ w moim odczuciu naj-lepiej uczyć się na bazie przykładów, dla-tego też, aby zobrazować podstawowe me-chanizmy, pokażę Ci drogi czytelniku, jak stworzyć prostą aplikację, która pozwo-li na blokowanie połączeń telefonicznych, a dodatkowo wyśle informacyjnego SMS'a do osoby, która próbowała się z Tobą skon-taktować.

Ale po kolei...Wiem, że najchętniej uruchomiłbyś IDE i za-czął pisać kod, na początek warto jednak po-wiedzieć słów kilka o tym, czym jest RIL. Pomyśl teraz chwilę o urządzeniach z syste-mem Windows Mobile – można wymienić dwie ich główne linie rozwojowe. Po pierw-sze są to urządzenia klasy PDA (ostatnimi czasy na wymarciu), z drugiej zaś strony urzą-dzenia, które można sklasyfikować jako tzw. Smartphony. Jaka jest pomiędzy nimi różni-ca? Oczywiście te drugie pozwalają na szero-ko rozumianą komunikację pomiędzy urzą-dzeniem a siecią GSM/UMTS. I tu na scenę wchodzi właśnie RIL (Radio Interface Layer).

Mówiąc o RIL, mamy na myśli pewne war-stwy abstrakcji, które oddzielają sprzęt od sys-temu – myślę, że jak najbardziej będzie tu na miejscu porównanie do sieciowego mode-lu OSI – zostało to pokazane na Rysunku 1. Na samym szczycie hierarchii mamy aplika-cje wchodzące w bezpośrednią komunikację z użytkownikiem i dostarczające mu funk-cjonalność związaną z telefoniczną stroną urządzeń z WM (zauważ, że pod pojęciem OEM nie kryją się tylko aplikacje dostarcza-ne przez producenta telefonu, ale również twoje). Szczebel niżej kryje się tzw. RIL Proxy – to właśnie z nim będziesz miał do czynie-nia, tworząc aplikacje oparte o Radio Interface Layer. Wywołując funkcje wchodzące w skład RIL API, tak naprawdę wysyłasz zapytania do Proxy, w odpowiedzi otrzymując wyniki. RIL Proxy tłumaczy twoje zapytania na od-powiednie polecenia typu DeviceIOControl, i wysyła je do sterownika modemu (radia). Na-stępnie te polecenia tłumaczone są na komen-dy AT, które wędrują do urządzenia, by tam zostać odpowiednio przetworzone.

Taka architektura ma wiele zalet. Po pierwsze wymaga na producentach sprzę-tu radiowego stworzenie sterowników w ta-ki sposób, aby były kompatybilne z RIL API-(jak już wspomniałem, cała komunikacja w systemie WM jest oparta właśnie o RIL'a). To z kolei pozwala producentom telefonów na wybór szerokiej gamy podzespołów, bez przejmowania się narzutem czasu niezbęd-nym do wypuszczenia produktu na rynek. Inną zaletą jest fakt asynchroniczności do-

RIL API

Radio Interface Layer (RIL) jest kluczowym elementem odpowiadającym za komunikacje pomiędzy systemem Windows Mobile a siecią GSM. W środowisku programistów WM panuje opinia, że API do obsługi RIL jest niejasne i trudne w użyciu. W tym artykule postaram się pokazać, że – jak powiada przysłowie – nie taki diabeł straszny jak go malują.

Dowiesz się:• Czym jest Radio Interface Layer;• Jak używać RIL API;• W jaki sposób stworzyć serwis systemowy

Windows Mobile;• W jaki sposób korzystać z rejestrów systemu

Windows Mobile.

Powinieneś wiedzieć:• Jak programować w języku C++;• Podstawy WINAPI.

Poziom trudności

w sercu telefonu

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

Page 13: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia12

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 13

stępu do sprzętu – pozwala to na uniknię-cie sytuacji, w której jedna aplikacja zawłasz-cza modem, odcinając dostęp do niego pozo-stałej części systemu.

Do 2004 roku RIL API przeznaczone by-ło do wewnętrznego użytku Microsoftu, a nawet po tej dacie Microsoft zalecał, aby używać API takich jak TAPI, SMS API, SIM API... Ostatnio jednak certyfikowany tre-ner Microsoftu stwierdził, że to podejście jest już nieaktualne, i gigant z Redmond po-prosił swoich trenerów, aby spopularyzowa-li bezpośrednie korzystanie z Radio Interfa-ce Layer. Jakie może to Tobie, jako programi-ście, przynieść korzyści? Po pierwsze – może się to przyczynić do wzrostu wydajności Two-jej aplikacji(wszystkie wspomniane powyżej API są tylko wrapperami na RIL).

Po drugie – wydaje mi się, że każdy, kto był zmuszony do pracy z chociażby z TAPI, doceni prostotę RIL. Kolejnym argumen-tem jest to, że przy użyciu jednego API jest się w stanie kontrolować całość telefonicz-

nej strony systemu. Pozwala to zaoszczę-dzić czas, zostaje bowiem zdjęta z progra-misty konieczność przyswajania wielu róż-niących się architekturami metod dostępu do poszczególnych funkcji.

Mimo że RIL API zostało upublicznio-ne, to jednak pliki nagłówkowe nie są nie-stety dołączone do Windows Mobile SDK. Aby dowiedzieć się skąd je zdobyć oraz jak zacząć pracę, spójrz proszę do ramki RIL: Szybki Start.

Najtrudniejszy pierwszy krokSpójrzmy na aplikację okiem przysłowiowego Kowalskiego. Przeciętny użytkownik nie jest świadom tego, jak wiele skomplikowanych działań podejmowanych jest za tzw. fronten-dem – dla niego aplikacja jest tożsama z tym, co może zobaczyć na wyświetlaczu. W tym artykule skupię się jednak na tym, co się dzie-je w tle – programowanie interfejsu użytkow-nika pozostawiając innym autorom. Aby jed-nak nie stawiać Cię przed koniecznością ręcz-

nego edytowania rejestrów (dlaczego wła-śnie rejestrów, niech pozostanie jeszcze przez chwilę tajemnicą), stworzyłem bardzo pro-stą aplikację, której ekran można zobaczyć na Rysunku 2 (kod aplikacji znajduje się na pły-cie DVD). Aplikacja ta daleka jest od dosko-nałości i przewidziana jest do użytku na plat-formie WM Professional, wydaje mi się jed-nak, że stanowić będzie dobry starter do dal-szych działań.

We wstępie określiliśmy mniej więcej, co będzie robić nasza aplikacja. Pora zastanowić się teraz nad znalezieniem odpowiedzi na py-tanie jak? – na to pytanie odpowiadać będzie-my krok po kroku, w myśl zasady po nitce do kłębka.

Rejestry systemoweRozpocznijmy od znalezienia sposobu na za-pisywanie konfiguracji naszej aplikacji. Przez konfigurację będziemy tu rozumieć trzy ro-dzaje informacji. Po pierwsze – czy nasza aplikacja powinna być aktualnie aktywna, czy nie? Po drugie – czy powinniśmy odrzu-

Rysunek 1. Schemat architektury RIL

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

���������

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

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

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

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

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

Rejestry – Typy wartości

• REG _ SZ – łańcuch tekstowy zakończony znakiem końca łańcucha( /0);• REG _ EXPAND _ SZ – łańcuch tekstowy zakończony znakiem końca łańcucha, zawierający w sobie zmienne środowiskowe(np. %PATH%);• REG _ MULTI _ SZ – seria łańcuchów tekstowych oddzielonych znakami końca łańcucha i zawierająca, zakończona podwójnym znakiem

końca łańcucha;• REG _ DWORD – 4-bajtowa wartość binarna;• REG _ BINARY – dowolna wartość binarna;• REG _ DWORD _ BIG _ ENDIAN – wartość typu DWORD przechowywana w formacie big endian;• REG _ DWORD _ LITTLE _ ENDIAN – to samo co REG_DWORD;• REG _ LINK – łącze symboliczne;• REG _ NONE – wartość nieokreślona;• REG _ RESOURCE _ LIST – lista zasobów sterownika.

Rysunek 2. Przykładowy interfejs użytkownika

Page 14: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia14

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 15

cać wszystkie przychodzące połączenia? Po trzecie – jeśli nie mamy odrzucać wszystkich połączeń, to czy są takie numery, które po-winniśmy blokować? W ramach konfiguracji musimy również uwzględnić treść wiadomo-ści SMS, która zostanie przesłana do odrzuco-nego numeru, jak również liczbę numerów, które będziemy blokować(tak dla ułatwienia sobie życia).

Zastanówmy się, w jaki sposób taką konfi-gurację możemy przechowywać? Pierwsze in-stynktownie nasuwające się rozwiązanie to zapisywanie jej w pliku. Historia zapisywa-nia konfiguracji w plikach ma długą trady-cję. Kiedyś aplikacje z PC-towych wersji Win-dowsa używały do tego celu plików INI. Oso-biście uważam, że jeśli chodzi o konfigurację opartą o pliki – doskonale sprawdza się for-

mat XML, jednak z powodu braku natywne-go wsparcia dla tego formatu, ta droga wydaje się zbyt pracochłonna.

Na szczęście system Windows Mobile-(podobnie jak i inne systemy z rodziny Win-dows) dostarcza bardzo przyzwoity system przechowywania konfiguracji – są to reje-stry systemowe. Rejestry można traktować jako pewien rodzaj bazy danych, zawierają-cej zbiór kluczy i wartości, przy czym klucze mogą zawierać w sobie zarówno inne klucze, jak i wartości. W ten sposób tworzy się drze-wiasta struktura, która w swoich liściach za-wiera konkretne wartości, a klucze stanowią ich gałęzie.

Od korzenia tego drzewa odchodzą trzy główne pnie: HKEY_LOCAL_MACHINE, HKEY_

CURRENT_USER, oraz HKEY_CLASSES_ROOT.

Pierwszy przechowuje konfigurację sprzę-tową oraz ustawienia aplikacji, drugi – jak wskazuje nazwa – przechowuje dane spe-cyficzne dla użytkownika , a ostatni – in-formacje na temat skojarzeń plików i obiek-tów OLE.

Ważną zaletą rejestrów jest to, że ich za-wartość zostaje zachowana po restarcie urzą-dzenia. Oczywiście nie należy umieszczać wartości gdziekolwiek – wprowadziłoby to tylko bałagan i utrudniło ewentualne ana-lizy. Zwyczajowo przyjęło się umieszczać konfigurację aplikacji w następującej ścież-ce rejestru: HKEY_LOCAL_MACHINE\Software\{Nazwa firmy}\{Nazwa aplika-cji}. Tak też uczynimy i my. Jako nazwę fir-my przyjmiemy SDJ, a jako nazwę aplikacji CallRejecter.

Rejestry mogą przechowywać wartości różnego typu, od wartości liczbowych ty-pu DWORD, przez łańcuchy tekstowe, aż po wartości binarnych(uprasza się jednak, aby nie próbować tam wpychać obrazków czy innych plików binarnych). Lista możli-wych typów wartości została przedstawio-na w ramce Rejestry – typy wartości. Należy w tym miejscu jeszcze wspomnieć o kilku zasadach, którymi powinno się kierować, dodając do rejestru własne klucze i warto-ści (ma to znaczący wpływ na wydajność dostępu do rejestrów):

• należy sprawić, by struktura zagnieżdżo-nych kluczy była tak płytka, jak to tylko możliwe;

• zamiast zagnieżdżonych kluczy, jeśli to tylko możliwe, należy używać war-tości;

• jedna wartość powinna przechowy-wać tak wiele informacji, jak to tyl-ko możliwe(np. jeśli będziemy posia-dać wartości dotyczące czasu i daty – warto rozważyć połączenie ich w jed-ną wartość.

Samo dostarczenie odpowiednio sformato-wanej struktury, pozwalającej na grupowa-nie różnych informacji, na niewiele by się zdało, gdyby nie funkcje pozwalające opero-wać na rejestrach. Do podstawowych operacji wystarczy używać czterech podstawowych funkcji: RegCreateKeyEx, RegQueryValueEx. RegSetValueEx, RegCloseKey. Pierwsza z nich tworzy klucz lub, jeśli ten istnieje – otwie-ra go. Aby używać dwóch kolejnych funkcji (odpowiednio: odczyt i zapis wartości), nale-ży podać uchwyt do klucza zwrócony przez pierwszą funkcję. Po zakończeniu operacji na kluczu należy zamknąć uchwyt do nie-go przy użyciu ostatniej z funkcji. Oczywi-ście zbiór funkcji służących operacjom na re-jestrach jest obszerniejszy(patrz odnośnik w ramce W Sieci). Tutaj wyjaśniono jedynie te najczęściej stosowane.

Listing 1. RegistryHelper.h

#ifndef _REGISTRYHELPER_H_

#define _REGISTRYHELPER_H_

#define MAX_BLOCKED_NUM 40

#define MAX_TEXT_LENGTH 256

#define MESSAGE_BUFFER_SIZE 161

#define SOFTWARE_KEY L"\\Software\\SDJ\\CallRejecter"

#define MESSAGE_VALUE L"Message"

#define REJECT_ALL_CALLS_VALUE L"RejectAllCalls"

#define BLOCKED_LIST_VALUE L"BlockedList"

#define ACTIVATE_SERVICE_VALUE L"ActivateService"

typedef struct callrejectersettings_tag{

DWORD dwActivateService;

DWORD dwBlockAll;

DWORD dwBlockedCount;

TCHAR tcMessage[MESSAGE_BUFFER_SIZE];

TCHAR tcBlockedList[MAX_BLOCKED_NUM][MAX_TEXT_LENGTH];

} CALLREJECTERSETTINGS, *LPCALLREJECTERSETTINGS;

VOID ConvertToStringsArray(TCHAR lpszOutput[][MAX_TEXT_LENGTH],LPTSTR

lpszInput,LPDWORD lpdwCount);

VOID ConvertToRegMultiSz(LPTSTR lpszOutput,TCHAR lpszInput[][MAX_TEXT_LENGTH],DWORD

nCount);

DWORD CalculateRegMultiSzSize(TCHAR lpszInput[][MAX_TEXT_LENGTH], DWORD nCount);

BOOL SetDwordValue(DWORD dwValue,LPTSTR lpszKey, LPTSTR lpszValueId);

BOOL SetSzValue(TCHAR lpszValue[MESSAGE_BUFFER_SIZE],LPTSTR lpszKey, LPTSTR

lpszValueId);

BOOL SetMultiSzValue(TCHAR lpszValue[][MAX_TEXT_LENGTH],DWORD nCount,LPTSTR

lpszKey,LPTSTR lpszValueId);

BOOL GetDwordValue(LPDWORD lpdwValue,LPTSTR lpszKey,LPTSTR lpszValueId);

BOOL GetMultiSzValue(TCHAR lpszValue[][MAX_TEXT_LENGTH],LPDWORD lpdwCount,LPTSTR

lpszKey,LPTSTR lpszValueId);

BOOL GetSzValue(TCHAR lpszValue[MESSAGE_BUFFER_SIZE],LPTSTR lpszKey,LPTSTR

lpszValueId);

BOOL ReadSettings(LPCALLREJECTERSETTINGS lpcrsSettings);

BOOL SaveSettings(CALLREJECTERSETTINGS crcSettings);

#endif

Page 15: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia14

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 15

Listing 2. RegistryHelper.cpp (Wycinek)

#include "stdafx.h"

#include <windows.h>

#include "RegistryHelper.h"

BOOL ReadSettings(LPCALLREJECTERSETTINGS lpcrsSettings)

{

memset(lpcrsSettings,0,sizeof(CALLREJECTERSETTINGS));

if (!GetDwordValue(&(lpcrsSettings->dwActivateService),SO

FTWARE_KEY,ACTIVATE_SERVICE_VALUE))

{

return FALSE;

}

if (!GetDwordValue(&(lpcrsSettings->dwBlockAll),SOFTWARE_

KEY,REJECT_ALL_CALLS_VALUE))

{

return FALSE;

}

if (!GetSzValue(lpcrsSettings->tcMessage,SOFTWARE_

KEY,MESSAGE_VALUE))

{

return FALSE;

}

if (!GetMultiSzValue(lpcrsSettings->tcBlockedList,&(lpcr

sSettings->dwBlockedCount),SOFTWARE_

KEY,BLOCKED_LIST_VALUE))

{

return FALSE;

}

return TRUE;

}

BOOL SaveSettings(CALLREJECTERSETTINGS crsSettings)

{

if(!SetDwordValue(crsSettings.dwBlockAll,SOFTWARE_

KEY,REJECT_ALL_CALLS_VALUE))

{

return FALSE;

}

if(!SetDwordValue(crsSettings.dwActivateService,SOFTWARE

_KEY,ACTIVATE_SERVICE_VALUE))

{

return FALSE;

}

if(!SetSzValue(crsSettings.tcMessage,SOFTWARE_

KEY,MESSAGE_VALUE))

{

return FALSE;

}

if(!SetMultiSzValue(crsSettings.tcBlockedList,crsSettings

.dwBlockedCount,SOFTWARE_KEY,BLOCKED_

LIST_VALUE))

{

return FALSE;

}

return TRUE;

}

BOOL GetDwordValue(LPDWORD lpdwValue,LPTSTR lpszKey,LPTSTR

lpszValueId)

{

BOOL bResult = FALSE;

HKEY hkey;

DWORD dwDisposition;

DWORD dwType;

DWORD dwSize;

DWORD dwRejectAllCalls;

if(ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE,

lpszKey,0, NULL, 0, 0, NULL, &hkey,

&dwDisposition))

{

dwType = 0;

dwSize = 0;

if (ERROR_SUCCESS == RegQueryValueEx(hkey, lpszValueId,

0, &dwType, NULL, &dwSize))

{

if ((REG_DWORD == dwType)&&(dwSize>0))

{

dwRejectAllCalls=0;

if (ERROR_SUCCESS == RegQueryValueEx(hkey,

lpszValueId, 0, &dwType,

reinterpret_cast<LPBYTE>(&dwRejectAll

Calls), &dwSize))

{

*lpdwValue=dwRejectAllCalls;

bResult = TRUE;

}

}

}

}

RegCloseKey(hkey);

return bResult;

}

BOOL SetDwordValue(DWORD dwValue,LPTSTR lpszKey, LPTSTR

lpszValueId)

{

BOOL bResult = FALSE;

HKEY hkey;

DWORD dwDisposition;

DWORD dwType;

DWORD dwSize;

DWORD dwRejectAllCalls;

if(ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE,

lpszKey, 0, NULL, 0, 0, NULL, &hkey,

&dwDisposition))

{

dwType = REG_DWORD;

dwRejectAllCalls=dwValue;

dwSize = sizeof(DWORD);

if (ERROR_SUCCESS == RegSetValueEx(hkey, lpszValueId,

0, dwType, reinterpret_cast<LPBYTE>(&

dwRejectAllCalls), dwSize))

{

bResult=TRUE;

}

}

RegCloseKey(hkey);

return bResult;

}

Page 16: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia16

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 17

Na Listingach 1 i 2 przedstawiony zo-stał fragment kodu wrappera, który posłuży nam do zapisywania/odczytywania ustawień naszej aplikacji. Kod na listingu drugim jest niekompletny, przedstawione zostały jedynie funkcje związane z odczytem/ zapisem war-tości typu REG_DWORD(przez analogie łatwo jest dopisać funkcje dla pozostałych typów wartości). Dodatkowo umieszczone na Li-stingu 2 zostały funkcje czytające/zapisujące całość naszych ustawień do/z struktury typu CALLREJECTERSETTINGS(zdefiniowanej na Li-stingu 1).

Prześledźmy krok po kroku operacje od-czytu(funkcja GetDwordValue) i zapisu(funk-cja SetDwordValue) umieszczone na Listingu 2. Rozpocznijmy od tej drugiej. W pierwszym kroku staramy się otworzyć interesujący nas klucz: RegCreateKeyEx(HKEY_LOCAL_MACHINE, lpszKey, 0, NULL, 0, 0, NULL, &hkey,

&dwDisposition)) Jako pierwszy parametr podajemy wartość klucza głównego, natomiast drugi to wartość podklucza. Gdybyśmy chcieli iterować od korzenia do konkretnej gałęzi drze-wa, mysielibyśmy wywoływać tę funkcję tak dłu-go, aż doszlibyśmy do interesującego nas klucza. Istnieje na szczęście prostszy sposób. Wystar-czy podać jeden z kluczy głównych, a następnie ścieżkę prowadzącą bezpośrednio do interesu-jącego nas klucza(w naszym przypadku będzie ona miała wartość(\Software\SDJ\CallRejecter).

Wartości argumentów ustawionych na zero lub NULL, oraz ostatniego argumentu – są ma-ło istotne. Pod wartość hKey podstawiony zo-stanie uchwyt do klucza, o który wnioskowa-liśmy (w naszym przypadku to będzie klucz CallRejecter). Następnie musimy zainicjali-zować parametry, które przekażemy do funk-cji: RegSetValueEx(hkey, lpszValueId, 0, dwType, reinterpret_cast<LPBYTE>(&dwRe

jectAllCalls), dwSize)). Funkcja ta przyj-muje wartość uchwytu do klucza, który pobra-liśmy w poprzednim kroku, a następnie łańcuch określający nazwę wartości, którą chcemy usta-wić. Kolejny argument jest zarezerwowany i mu-si być ustawiony na zero, następnie musimy po-dać typ wartości(dla tej konkretnej sytuacji bę-dzie to REG_DWORD), a na końcu bufor, któ-ry zawiera wartość do zapisu. W ostatnim para-metrze przekazujemy rozmiar bufora. Finalnym krokiem jest zwolnienie uchwytu do wcześniej pobranego klucza (RegCloseKey(hkey)).

Jeśli teraz przyjrzysz się funkcjom odczy-tującym, zauważysz zapewne, że w zasadzie nie różnią się one znacząco. Jedyną różnicą (oprócz użycia funkcji RegQueryValueEx w miejsce funkcji RegSetValueEx) jest to, iż tym razem nie musimy inicjalizować konkretnymi wartościami zmiennych odpowiedzialnych za typ oraz rozmiar na konkretne wartości. O ile w przypadku wartości typu REG_DWORD rozmiar bufora będzie miał zawsze tę samą

wartość (sizeof(DWORD), o tyle już w przy-padku wartości typu REG_SZ lub REG_MUL-TI_SZ musimy wcześniej zaalokować odpo-wiednią ilość pamięci. W tym celu przy pierw-szym użyciu dodajemy operację odczytu, w której zamiast wskaźnika na bufor, przekazu-jemy wartość NULL.

W ten sposób dokonamy jedynie odczy-tu rozmiaru danej wartości (w bajtach) oraz jej typu.

Serwisy systemoweMając ustalony sposób, w jaki będziemy prze-chowywać ustawienia dla naszej aplikacji, wy-padałoby zastanowić się, co będzie stanowiło jej szkielet. Jak już wspomniałem, chcielibyśmy, aby nasza aplikacja działała w trybie 24/7, czy-li zawsze, kiedy uruchomione jest urządzenie. Możemy to zrobić na dwa sposoby. Pierwszym jest standardowa aplikacja, która po uruchomie-niu będzie ustawiać się w tryb oczekiwania na nadejście odpowiednich komunikatów, a na-stępnie umieścić ją w sekcji autostart urządze-nia. Rozwiązanie to jest jednak mało eleganc-kie - nie powinno się tworzyć w ten sposób pro-gramów, które nie komunikują się bezpośred-nio z użytkownikiem. Inżynierowie projektują-cy system Windows Mobile w celu realizacji za-dań uruchamianych w tle dostarczyli programi-stom mechanizm w postaci tzw. serwisów syste-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 wersji Windows CE 4.2 .NET programiści w ce-Rysunek 3. Przykładowy wpis do rejestrów dotyczący usługi LgeCCoreDrv

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

• Context (REG _ DWORD) – wartość inicjalizująca przekazywana do procedury inicjalizacyjnej;• Description (REG _ SZ) – opis serwisu;• DisplayName (REG _ SZ) – nazwa serwisu;• Dll (REG _ SZ) – dynamicznie łączona biblioteka (DLL) zawierająca serwis;• Flags (REG _ DWORD) – określa zbiór flag używanych do modyfikacji zachowania funkcji ActivateServices. Oto poprawne wartości flag:

• DEVFLAGS _ NONE (0x00000000): brak określonych flag;• DEVLFAGS _ UNLOAD (0x00000001): wyładuj serwis po powrocie z funkcji xxx _ Init;• DEVFLAGS _ LOADLIBRARY (0x00000002): skorzystaj z funkcji LoadLibrary do załadowania pliku DLL usługi;• DEVFLAGS _ NOLOAD (0x00000004): nie ładuj serwisu; • DEVFLAGS _ TRUSTEDCALLERONLY (0x00010000) : serwis może być wywołany tylko przez zaufane procesy;

• Index (REG _ SZ) – indeks (instancji serwisu);• Keep (REG _ DWORD) – jeśli ustawiony na 0, serwis zostanie wyładowany natychmiast po inicjalizacji;• Order (REG _ DWORD) – kolejność, w jakiej ładowane są serwisy, serwis o najniższej wartości ładowany jest w pierwszej kolejności;• Prefix (REG _ SZ) – trzyliterowy prefiks definiujący usługę – wraz z wartością indeksu pozwala na odwołanie się do konkretnej instancji

serwisu.

Page 17: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia16

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 17

lu realizacji zadań serwisów zmuszeni byli uży-wać systemu sterowników – dopiero w tej wersji pojęcie to zostało wprowadzone do rodziny CE. W związku z tymi zaszłościami historycznymi usługi odziedziczyły po sterownikach pewne ce-chy, jak np. punkty wejścia, lub również trzylite-rowy prefiks, który wraz z numerem instancji serwisu służy do pobrania uchwytu.

Skąd jednak zarządca serwisów systemo-wych wie, że dany serwis ma zostać załadowa-ny? I czy samo załadowanie serwisu do pamię-ci oznacza, że będzie on działał? Odpowiem w pierwszej kolejności na pytanie drugie: nie, sa-ma obecność usługi w pamięci nie oznacza jej działania (oczywiście w sensie spełniania funk-cji, dla której serwis został stworzony, w dal-szym ciągu serwis jest gotowy do odbierania komunikatów od procesu Services.exe). Dla-czego? Otóż dlatego, że serwis może znajdo-wać się w kilku stanach: może być uruchomio-ny lub też wstrzymany, dochodzi do tego jesz-cze kilka mniej istotnych stanów. Wróćmy więc do pierwszego pytania. Spójrz proszę na Rysunek 3. Przedstawiony tam został wycinek systemowych rejestrów zawierający wpisy dla usługi LgeCCoreDrv. Znajdują się tam infor-macje na temat wszystkich ważnych właściwo-ści związanych z inicjalizacją serwisu (spójrz na ramkę Serwisy – rejestry). Jeśli podobny wpis dodasz do rejestru dla swojego serwisu (w ścieżce HKEY_LOCAL_MACHINE\Services\{Nazwa serwisu}), wówczas zostanie on za-ładowany wraz ze startem systemu (pamiętaj jednak, że załadowany to nie to samo, co uru-chomiony). Innym sposobem na załadowanie serwisu bez konieczności tworzenia wpisu w rejestrze jest użycie funkcji RegisterService (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 przeze mnie GUI pozwoliłem sobie na uży-cie właśnie tego ostatniego podejścia. Funkcja ActivateService przyjmuje dwa argumenty: pierwszy określający nazwę klucza rejestrów zawierającego informację o usłudze, drugi jest natomiast zarezerwowany i powinien zostać ustawiony na zero.

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-ces.exe na komunikację z załadowaną biblioteką DLL. Istnieje dziesięć standardowych punktów wejścia: xxx_Init, xxx_Deinit, xxx_Open,

xxx_Close, xxx_Read, xxx_Write, xxx_

Seek, xxx_IOControl, xxx_PowerDown oraz xxx_PowerUp. Jak już wspomniałem, jest to spa-dek z czasów, gdy jako dostawców usług używa-no sterowników. W procesie implementacji xxx w nazwach funkcji zamieniamy oczywiście na trzyliterowy prefiks, pod jakim serwis funkcjo-nuje w systemie.

Na szczęście nie musimy implementować wszystkich funkcji. Pominiemy funkcje wyni-

kające z podejścia do serwisu jako pliku (Open, Close, Read, Write, Seek). Na Listin-gu 3 został przedstawiony szkielet naszego ser-wisu. Zauważmy, że w miejsce xxx wstawi-liśmy trzyliterowy prefiks CRS (dla ciekaw-skich – wymyśliłem go jako akronim od Call Rejecter Service). Musimy jeszcze sprawić, aby nasze funkcje były widoczne na zewnątrz pliku DLL. Możemy to zrobić dwojako, po pierwsze możemy do tego użyć dyrektywy __declspec(dllexport) przy definicji funkcje; drugim sposobem jest dołączenie do projektu pliku DEF. Taki przykładowy plik definicji zo-stał przedstawiony na Listingu 4.

Czas wyjaśnić znaczenie poszczególnych funkcji, zanim przejdziemy do ich implemen-

tacji. CRS_Init – jak nietrudno się domyślić, ta funkcja jest wywoływana podczas inicjowania serwisu, co może nastąpić w dwóch sytuacjach: w wyniku wywołania funkcji RegisterService lub w momencie, w którym następuje inicjali-zacja procesu Services.exe. W tym drugim przy-padku jako argument do funkcji przekazana zo-stanie umieszczona w rejestrach wartość Con-text. W przypadku powodzenia, powinna zo-stać zwrócona wartość niezerowa, w przeciwnej sytuacji usługa zostanie wyładowana.

Nie trzeba być orłem, aby odgadnąć prze-znaczenie funkcji CRS_Deinit. Jako jedy-ny argument przyjmuje ona wartość zwróco-ną przez poprzednią funkcję. Pamiętajmy, że skoro funkcja CRS_Init zwraca wartość typu

Listing 3. CallRejecterService.cpp

#include "stdafx.h"

#include <windows.h>

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,

LPVOID lpReserved )

{

return TRUE;

}

DWORD CRS_Init(DWORD dwData)

{

DWORD dwContext = 1;

return dwContext;

}

DWORD CRS_Deinit(DWORD dwContext)

{

return TRUE;

}

DWORD CRS_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE

pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)

{

return TRUE;

}

void CRS_PowerDown(DWORD dwContext) {

return;

}

void CRS_PowerUp(DWORD dwContext)

{

return;

}

Listing 4. CallRejecterService.def

LIBRARY "CallRejecterService"

EXPORTS

CRS_Init

CRS_Deinit

CRS_IOControl

CRS_PowerDown

CRS_PowerUp

Page 18: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia18

Programowanie Windows Mobile

DWORD, to w łatwy w sposób można na nią rzu-tować wskaźnik na dowolną strukturę czy typ. Dzięki takiemu rozwiązaniu możemy unik-nąć stosowania zmiennych globalnych.

Para funkcji CRS_PowerUp i CRS_PowerDown służy do przygotowania serwisu na przejście urządzenia w stan niskiego poboru energii, a potem na przywrócenie odpowiedniego sta-nu po powrocie do normalnego stanu zasila-nia. Funkcje te przyjmują jako argument war-tość zwróconą przez CRS_Init (vide unika-nie zmiennych globalnych). Powinieneś za-wsze obsłużyć w swoim serwisie te dwie funk-cje, tak aby możliwe było przed przejściem w stan obniżonego zużycia energii zwolnienie jak największej liczby zasobów przywłaszczo-nych przez usługę.

Ostatnia funkcja (CRS_IOControl) odpo-wiada za komunikację aplikacji zewnętrz-nych z usługą. To właśnie tu trafiają komu-nikaty wysłane chociażby przy użyciu funk-cji ServiceIOControl. Na Listingu 5 zo-stała umieszczona wypełniona funkcja CRS_IOControl naszego serwisu.

W naszym prostym serwisie obsłużymy je-dynie dwa komunikaty, zachęcam jednak do eksperymentów z innymi, jak również do de-finiowania swoich. Pierwszym z obsłużonych komunikatów jest IOCTL_SERVICE_QU-ERY_CAN_DEINIT.

Może być on np. następstwem wywoła-nia funkcji DeregisterService (której zada-niem jest wyładowanie serwisu). Jeżeli usłu-ga z jakiegoś powodu nie może być wyładowa-na, wówczas w buforze wyjściowym powinna ustawić zero, jeżeli wartość w buforze będzie niezerowa, oznacza to, że będzie mogła zostać wywołana funkcja CRS_Deinit.

Drugim komunikatem obsłużonym w na-szym serwisie jest IOCTL_SERVICE_RE-FRESH, którym subtelnie sugerujemy serwi-sowi, że powinien ponownie wczytać swo-ją konfigurację, ponieważ uległa ona zmianie. Co prawda nie zdefiniowaliśmy jeszcze funk-cji UpdateConfiguration, ale zapewniam Cię, że gdybyś zdefiniował chociażby puste ciało tej funkcji, dokonał odpowiednich wpisów w re-jestrach i wrzucił skompilowaną bibliotekę na urządzenie, byłbyś w stanie uruchomić naszą usługę (a nawet udałoby Ci się ją zatrzymać)

Nuda, nic się nie dzieje...Gdybyśmy poprosili inżyniera Mamonia o ko-mentarz na temat naszego oprogramowania, tak pewnie by je właśnie podsumował i nie ma w tym nic dziwnego, zważywszy na fakt, iż w tej chwili nasza usługa nie robi właściwie nic (a już zdecydowanie nie to, do czego ją prze-widzieliśmy). Pora więc nieco ożywić akcję i wprowadzić wreszcie na scenę głównego boha-tera tego artykułu. Najpierw jednak spójrzmy na Listing 6 – umieściłem na nim wypełnione ciała pozostałych funkcji, nie przejmujemy się przy tym faktem, że wprowadzone zostały ko-

Listing 5. Funkcja CRS_IOControl

DWORD CRS_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE

pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)

{

switch(dwCode)

{

case IOCTL_SERVICE_QUERY_CAN_DEINIT:

{

if ((pBufOut) && (dwLenOut >= sizeof (BYTE)))

{

*pBufOut = 1;

if (pdwActualOut)

{

*pdwActualOut = sizeof(BYTE);

}

}

}

case IOCTL_SERVICE_REFRESH:

{

UpdateConfiguration();

}

break;

}

return TRUE;

}

Listing 6. Brakująca zawartość pliku CallRejecterService.cpp

#include <Service.h>

#include "RilHelper.h"

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,

LPVOID lpReserved )

{

return TRUE;

}

DWORD CRS_Init(DWORD dwData)

{

DWORD dwContext = 0;

if (UpdateConfiguration())

{

if SUCCEEDED(InitializeCallRejecter())

{

dwContext = 1;

}

}

return dwContext;

}

DWORD CRS_Deinit(DWORD dwContext)

{

DeinitializeCallRejecter();

return TRUE;

}

void CRS_PowerDown(DWORD dwContext) {

DeinitializeCallRejecter();

return;

}

void CRS_PowerUp(DWORD dwContext)

{

InitializeCallRejecter();

return;

}

Page 19: SDJ Extra 34 Biblia

www.sdjournal.org 19

lejne funkcje, o których implementacji nic nie wiemy. Za chwilę wszystko stanie się jasne (o ile już nie jest).

Na Listingach 7 i 8 została przedstawiona po-została część naszego serwisu (ta, która wresz-cie coś robi). Jak wspomniałem we wstępie, RIL API ma wśród wielu programistów opi-nię, nazwijmy to delikatnie, niezbyt przyjemne-go – dzieje się tak prawdopodobnie dlatego, że z jednej strony większa część funkcji wchodzą-cych w jego skład ma charakter asynchroniczny, z drugiej zaś sama architektura jest typowym przykładem event-driven programming.

Chcąc skorzystać z dobrodziejstw dostar-czanych przez Radio Interface Layer, musisz w pierwszej kolejności zarejestrować połączenie z RIL Proxy. Robi się to przy użyciu funkcji RIL_Initialize. Funkcja ta przyjmuje jako pierw-szy argument numer Proxy, do którego chcemy się podłączyć (nie wiem, jaka jest prawidłowość, ale w tym miejscu działa przeważnie tylko war-tość 1). Kolejne dwa parametry to wskaźniki na funkcje, za pomocą których RIL Proxy będzie się komunikowało z naszą aplikacją, następnie musimy podać klasę notyfikacji, które chcieli-byśmy otrzymywać. Następny parametr typu DWORD to wartość, którą będziemy przekazy-wać do dwóch wspomnianych wcześniej funk-cji – pamiętaj, że podobnie jak w przypadku

Listing 7. RilHelper.h

#ifndef _RILHELPER_H_

#define _RILHELPER_H_

#include "ril.h"

#define RIL_TIMEOUT 3000

#define HANGUP_SUCCESS L"HangUpSuccess"

#define HANGUP_FAIL L"HangUpFail"

#define GETMSGCONFIG_SUCCESS L"GetMsgConfigSuccess"

#define GETMSGCONFIG_FAIL L"GetMsgConfigFail"

#define SENDMSG_SUCCES L"SendMsgSuccess"

#define SENDMSG_FAIL L"SendMsgFail"

enum RilFunctions {HANGUP,GETMSGCONFIG,SENDMESSAGE};

HRESULT InitializeCallRejecter();

HRESULT DeinitializeCallRejecter();

BOOL IsOnBlockedList(LPTSTR lpszNumber);

BOOL UpdateConfiguration();

VOID RilNotifyCallback(DWORD dwCode, const void* lpData, DWORD cbData, DWORD dwParam );

VOID RilResultCallback(DWORD dwCode, HRESULT hrCmdID, const void* lpData, DWORD

cbData, DWORD dwParam);

HRESULT GetMsgConfig();

HRESULT SendSMS(RILADDRESS raDest);

#endif

RIL API – w sercu telefonu

R E K L A M A

Page 20: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia20

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 21

Listing 8. RilHelper.cpp

#include "stdafx.h"

#include "RilHelper.h"

#include "RegistryHelper.h"

HRESULT hrResults[3]={E_FAIL,E_FAIL,E_FAIL};

HANDLE hEvents[6]={NULL,NULL,NULL,NULL,NULL};

HRIL hRil=NULL;

LPRILMSGCONFIG g_lprmcMsgConfig = NULL;

CALLREJECTERSETTINGS g_crsSettings={};

HRESULT GetMsgConfig()

{

HRESULT hr = E_FAIL;

DWORD dwEvent=0;

if (hRil)

{

hr = RIL_GetMsgConfig(hRil);

hrResults[GETMSGCONFIG] = hr;

if(SUCCEEDED(hr))

{

dwEvent = WaitForMultipleObjects(2,&hEvents[2*GETMS

GCONFIG],FALSE,100*RIL_TIMEOUT);

switch(dwEvent)

{

case WAIT_OBJECT_0:

hr = S_OK;

break;

default:

hr = E_FAIL;

}

}

}

return hr;

}

HRESULT SendSMS(RILADDRESS raDest)

{

RILMESSAGE rm={};

HRESULT hr = E_FAIL;

DWORD dwEvent = 0;

if (hRil)

{

rm.cbSize = sizeof(RILMESSAGE);

rm.dwParams = RIL_PARAM_M_SVCCTRADDRESS | RIL_PARAM_

M_TYPE | RIL_PARAM_M_DESTADDRESS

| RIL_PARAM_M_PROTOCOLID |RIL_PARAM_

M_MSGLENGTH | RIL_PARAM_M_FLAGS |

RIL_PARAM_M_VPFORMAT | RIL_PARAM_

M_DATACODING | RIL_PARAM_M_VP |

RIL_PARAM_M_MSG;

rm.raSvcCtrAddress.cbSize = g_lprmcMsgConfig-

>raSvcCtrAddress.cbSize;

rm.raSvcCtrAddress.dwNumPlan = g_lprmcMsgConfig->raSvc

CtrAddress.dwNumPlan;

rm.raSvcCtrAddress.dwParams = g_lprmcMsgConfig->raSvcC

trAddress.dwParams;

rm.raSvcCtrAddress.dwType = rm.raSvcCtrAddress.dwType;

_tcscpy_s(rm.raSvcCtrAddress.wszAddress,256,g_

lprmcMsgConfig->raSvcCtrAddress.wszAd

dress);

rm.dwType = RIL_MSGTYPE_OUT_SUBMIT;

rm.dwFlags = RIL_MSGFLAG_NONE;

rm.msgOutSubmit.dwProtocolID = RIL_MSGPROTOCOL_SMETOSME;

rm.msgOutSubmit.raDestAddress.cbSize = raDest.cbSize;

rm.msgOutSubmit.raDestAddress.dwNumPlan =

raDest.dwNumPlan;

rm.msgOutSubmit.raDestAddress.dwParams = raDest.dwParams;

rm.msgOutSubmit.raDestAddress.dwType = raDest.dwType;

_tcscpy_s(rm.msgOutSubmit.raDestAddress.wszAddress,256

,raDest.wszAddress);

rm.msgOutSubmit.rmdDataCoding.cbSize = sizeof(RILMSGDCS);

rm.msgOutSubmit.rmdDataCoding.dwParams = RIL_PARAM_

MDCS_TYPE|RIL_PARAM_MDCS_ALPHABET;

rm.msgOutSubmit.rmdDataCoding.dwType = RIL_DCSTYPE_

GENERAL;

rm.msgOutSubmit.rmdDataCoding.dwAlphabet = RIL_

DCSALPHABET_DEFAULT;

rm.msgOutSubmit.rmdDataCoding.dwMsgClass = RIL_

DCSMSGCLASS_0;

rm.msgOutSubmit.rmdDataCoding.dwFlags = RIL_DCSFLAG_NONE;

SYSTEMTIME curTime;

GetLocalTime(&curTime);

rm.msgOutSubmit.stVP.wYear = curTime.wYear;

rm.msgOutSubmit.stVP.wMonth = curTime.wMonth;

rm.msgOutSubmit.stVP.wDayOfWeek = curTime.wDayOfWeek;

rm.msgOutSubmit.stVP.wDay = curTime.wDay;

rm.msgOutSubmit.stVP.wHour = curTime.wHour;

rm.msgOutSubmit.stVP.wMinute = curTime.wMinute;

rm.msgOutSubmit.stVP.wSecond = curTime.wSecond;

rm.msgOutSubmit.stVP.wMilliseconds =

curTime.wMilliseconds;

rm.msgOutSubmit.cchMsgLength = wcstombs(reinterpret_

cast<CHAR *>(rm.msgOutSubmit.rgbMsg),g

_crsSettings.tcMessage,256);

hr = RIL_SendMsg(hRil,&rm,RIL_SENDOPT_NONE);

hrResults[SENDMESSAGE] = hr;

if(SUCCEEDED(hr))

{

dwEvent = WaitForMultipleObjects(2,&hEvents[2*SENDM

ESSAGE],FALSE,RIL_TIMEOUT);

switch(dwEvent)

{

case WAIT_OBJECT_0:

hr = S_OK;

break;

default:

hr = E_FAIL;

}

}

}

return hr;

}

VOID RilResultCallback(DWORD dwCode, HRESULT hrCmdID, const void*

lpData, DWORD cbData, DWORD dwParam)

{

if (dwCode = RIL_RESULT_OK)

{

if (hrCmdID==hrResults[HANGUP])

{

SetEvent(hEvents[2*HANGUP]);

}

Page 21: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia20

Programowanie Windows MobileRIL API – w sercu telefonu

www.sdjournal.org 21

Listing 8 cd. RilHelper.cpp

if (hrCmdID==hrResults[GETMSGCONFIG])

{

if (lpData)

{

g_lprmcMsgConfig = reinterpret_cast<LPRILMSGCONF

IG>(LocalAlloc(LHND,cbData));

if (g_lprmcMsgConfig)

{

CopyMemory(g_lprmcMsgConfig,lpData,cbData);

SetEvent(hEvents[2*GETMSGCONFIG]);

}

}

}

if (hrCmdID==hrResults[SENDMESSAGE])

{

DWORD dwResult=reinterpret_cast<DWORD>(lpData);

SetEvent(hEvents[2*SENDMESSAGE]);

}

}

else

{

if (hrCmdID==hrResults[HANGUP])

{

SetEvent(hEvents[2*HANGUP+1]);

}

if (hrCmdID==hrResults[GETMSGCONFIG])

{

SetEvent(hEvents[2*GETMSGCONFIG+1]);

}

if (hrCmdID==hrResults[SENDMESSAGE])

{

SetEvent(hEvents[2*SENDMESSAGE+1]);

}

}

}

VOID RilNotifyCallback(DWORD dwCode, const void* lpData,

DWORD cbData, DWORD dwParam )

{

switch (dwCode)

{

case RIL_NOTIFY_CALLERID:

{

LPRILREMOTEPARTYINFO lpRilRemotePartyInfo =

reinterpret_cast<LPRILREMOTEPARTYINFO

>(const_cast<LPVOID>(lpData));

if (IsOnBlockedList(lpRilRemotePartyInfo-

>raAddress.wszAddress))

{

if (SUCCEEDED(RIL_Hangup(hRil)))

{

SendSMS(lpRilRemotePartyInfo->raAddress);

}

}

}

}

}

HRESULT InitializeCallRejecter()

{

HRESULT hr = E_FAIL;

hEvents[2*HANGUP]=CreateEvent(NULL,FALSE,FALSE,HANGUP_

SUCCESS);

hEvents[2*HANGUP]=CreateEvent(NULL,FALSE,FALSE,HANGUP_FAIL);

hEvents[2*GETMSGCONFIG]=CreateEvent(NULL,FALSE,FALSE,GET

MSGCONFIG_SUCCESS);

hEvents[2*GETMSGCONFIG+1]=CreateEvent(NULL,FALSE,FALSE,G

ETMSGCONFIG_FAIL);

hEvents[2*SENDMESSAGE]=CreateEvent(NULL,FALSE,FALSE,SEND

MSG_SUCCES);

hEvents[2*SENDMESSAGE+1]=CreateEvent(NULL,FALSE,FALSE,SE

NDMSG_FAIL);

hr = RIL_Initialize(1,RilResultCallback,RilNotifyCallback

,RIL_NCLASS_SUPSERVICE,NULL,&hRil);

if (SUCCEEDED(hr))

{

hr = GetMsgConfig();

}

return hr;

}

HRESULT DeinitializeCallRejecter()

{

HRESULT hr = E_FAIL;

for (DWORD i=0;i<6;i++)

{

CloseHandle(hEvents[i]);

}

g_lprmcMsgConfig = reinterpret_cast<LPRILMSGCONFIG>(Loc

alFree(reinterpret_cast<HLOCAL>(g_

lprmcMsgConfig)));

hr = RIL_Deinitialize(hRil);

return hr;

}

BOOL UpdateConfiguration()

{

memset (&g_crsSettings,0,sizeof(CALLREJECTERSETTINGS));

return ReadSettings(&g_crsSettings);

}

BOOL IsOnBlockedList(LPTSTR lpszNumber)

{

BOOL bResult = FALSE;

if (g_crsSettings.dwBlockAll!=0)

{

bResult = TRUE;

}

else

{

for (DWORD i=0;i<g_crsSettings.dwBlockedCount;i++)

{

if(!_tcscmp(lpszNumber,g_crsSettings.tcBlockedList[i]))

{

bResult = TRUE;

break;

}

}

}

return bResult;

}

Page 22: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia22

Programowanie Windows Mobile

serwisów, możesz w tym miejscu rzutować na typ DWORD chociażby bufor, w którym będziesz przekazywał dodatkowe argumenty lub do któ-rego powinien być zapisany wynik. Ostatnim parametrem jest wskaźnik do uchwytu do RIL Proxy, który zostanie nam zwrócony. Uchwytu tego będziemy używać, aby wywoływać funk-cje RIL. Należy pamiętać, aby uchwyt ten bez-względnie zwolnić w momencie, kiedy nie bę-dzie już potrzebny. Robi się to przy użyciu funk-cji RIL_Deinitialize.

Dwie funkcje, o których wspomniałem przed chwilą, w kontekście parametrów prze-kazanych do funkcji RIL_Initialize to RilNotifyCallback oraz RilResultCallback. Pierwsza z nich odpowiada za przetwarzanie no-tyfikacji, na których nasłuchiwanie zarejestro-waliśmy się, podając klasę notyfikacji w wywo-łaniu funkcji RIL_Initialize. Funkcja ta przyj-muje jako parametry cztery wartości. Pierwszą jest kod notyfikacji, druga to wskaźnik na bu-for, który zawiera dodatkowe informacje niesio-ne wraz z notyfikacją, trzecia to rozmiar tego bu-fora w bajtach, ostatnia zaś to wartość zadeklaro-wana podczas inicjalizacji jako przekazywana do wywołań callbacków. W naszym przykładzie za-rejestrowaliśmy się na nasłuchiwanie notyfika-cji z grupy RIL_NCLASS_SUPSERVICE. Jed-ną z nich jest notyfikacja RIL_NOTIFY_CAL-LERID, która zostaje wysłana w momencie przy-chodzącego połączenia. Wskaźnik lpData wska-zuje na strukturę typu RILREMOTEPARTYINFO , której jedną ze składowych jest numer, z które-go przychodzi do nas połączenie.

Znając tę wartość, możemy porównać ją z listą blokowanych numerów i podjąć decyzję o odrzuceniu takiego połączenia. Połączenie możemy odrzucić przy pomocy funkcji RIL_HangUp, która jako jedyny parametr przyjmu-je uchwyt do RIL Proxy, potem możemy wy-słać SMS z informacją o tym, dlaczego połącze-nie odrzuciliśmy. Na potrzeby tej przykłado-wej aplikacji stworzyłem w tym celu funkcję SendSms, której przyjrzymy się później.

Druga z funkcji (RilResultCallback) słu-ży jako punkt powrotu z wywołań asynchro-nicznych funkcji wchodzących w skład RIL API. Przyjmuje ona o jeden parametr wię-cej niż RilNotifyCallback – jest to hrCmdID. Pierwszy z parametrów tej funkcji niesie ze so-bą informację na temat tego, czy asynchronicz-na funkcja, którą wywoływaliśmy, zakończyła się powodzeniem (wówczas przyjmie on war-tość RIL_RESULT_OK). Drugi z parametrów po-zwala na identyfikację, którą z funkcji wywoły-waliśmy. Mechanizm ten działa w następujący

sposób: Każdorazowo gdy wywołujemy funkcję asynchroniczną z zestawu RIL API, zwraca ona natychmiast wartość typu HRESULT. Jeśli wartość ta jest ujemna, to możemy ją zinterpretować jako nieudane wywołanie i odszukać powód niepo-wodzenia w tabeli błędów. Jeśli zaś wartość jest nieujemna, to będzie ona stanowiła identyfika-tor przekazany do funkcji RilResultCallback właśnie jako hrCmdID. Znaczenie pozostałych parametrów jest zbieżne z parametrami przeka-zanymi do funkcji RilNotifyCallback.

Asynchroniczność funkcji często jest po-wodem do narzekań, można ją jednak łatwo ujarzmić. Jako przykład niech posłuży stwo-rzona przeze mnie funkcja GetMsgConfig. Jej zadaniem jest odczytać konfigurację centrum SMS, tak abyśmy mogli wysyłać później nasze wiadomości. Funkcja ta tak naprawdę jest opa-kowaniem funkcji RIL_GetMsgConfig, mają-cym na celu właśnie ominięcie asynchronicz-ności. Po wywołaniu tej funkcji oczekujemy bowiem na przyjście zdarzenia sygnalizujące-go. Przyjmując rozsądny czas oczekiwania, je-steśmy w stanie po jego upływie z dużą dozą prawdopodobieństwa stwierdzić, że ponieśli-śmy porażkę i nie ma sensu dalej czekać. Cała faktyczna obsługa rezultatu wywołania funk-cji RIL_GetMsgConfig ma oczywiście miej-sce w funkcji RilResultCallback. Jeśli funk-cja powiedzie się, to otrzymamy wskaźnik na strukturę typu RILMSGCONFIG, zawierają-cą potrzebne nam ustawienia. Skoro strukturę taką będziemy już posiadać, możemy wykonać zdarzenie oznaczające powodzenie.

Kolejnym przykładem synchronicznego wrappera jest funkcja SendSMS, choć w tym wypadku i tak nie troszczymy się o rezul-tat. Funkcja ta wykorzystuje rilowską funk-cję RIL_SendMsg. W tym miejscu pora na ma-łą uwagę. Ponieważ mimo dostarczenia jed-nolitego API faktyczny ciężar implementa-cji funkcjonalności i tak leży po stronie do-stawcy sterownika modemu, może się zda-rzyć, że na niektórych urządzaniach część funkcji nie będzie działać poprawnie. Tak jest właśnie w przypadku RIL_SendMsg. Je-śli na twoim urządzeniu funkcja ta nie bę-dzie dawała żadnego efektu, spróbuj ją zastą-pić przez wywołanie funkcji SmsSendMessage wchodzącej w skład SMS API. Przyjrzyjmy się więc przez chwilę ciału funkcji SendSMS. Ujawnia się tu dodatkowy minus związa-ny z RILem, czyli wszechobecna koniecz-ność wypełniania skomplikowanych struk-tur. W tym konkretnym przypadku musi-my wypełnić strukturę RILMESSAGE, która

składa się oprócz składowych prostych rów-nież ze struktur (np. struktury odpowiada-jącej za konfigurację centrum obsługi SMS), oraz jednej unii (zawierającej struktury cha-rakterystyczne dla różnych typów wiadomo-ści). Dochodzą do tego jeszcze różnorakie sta-łe (jak np. RIL_MSGTYPE_OUT_SUBMIT – odpo-wiadająca wiadomości przeznaczonej do wy-słania, czy RIL_DCSMSGCLASS_0 – określająca, że wiadomość ma zostać wyświetlona bezpo-średnio na wyświetlaczu telefonu). W przy-padku każdej struktury trzeba jeszcze okre-ślić, które jej składowe są znaczące (i znów przy użyciu stałych). Efekt jest taki, że bez otwartej cały czas dokumentacji nie jesteśmy w stanie tego zrobić. Na szczęście wszystko zostało udokumentowane w sposób profesjo-nalny (tego typu dokumentacji zazdroszczą nam nawet najzagorzalsi zwolennicy Symbia-na - odnośnik znajdziesz w ramce W Sieci), a wraz z nabywaniem wprawy, dzięki podpo-wiedziom InteliSensa i własnej intuicji, czas tworzenia nowych rozwiązań drastycznie ulega skróceniu.

PodsumowanieW niniejszym artykule starałem się pokazać, że RIL pomimo swojego ogromu i pozorne-go skomplikowania, daje się jednak ujarz-mić. Oczywiście to, co zostało tu omówio-ne, to zaledwie wierzchołek góry lodowej. Nie omówiłem tu chociażby funkcji pozwa-lających na dostęp do informacji zawartych na karcie SIM czy też bardzo ciekawej funk-cji RIL_DevSpecific, która pozwala na do-stęp do ukrytych przez producenta sterow-ników modemu funkcjonalności (funkcje te jednakże są indywidualne dla urządzeń i niestety, jeśli nie współpracujesz z produ-centem telefonów, to prawdopodobnie nie uda Ci się do nich dotrzeć). Mam jednak-że nadzieję, że udało mi się zachęcić do sa-modzielnego zgłębiania tajników RIL'a i nie skreślania go na starcie.

PRZEMYSŁAW NOGAJPracuje na stanowisku Junior C++ Developer w firmie BLStream, wchodzącej w skład Grupy BLStream. Przemek specjalizuje się w technolo-giach związanych z produkcją oprogramowania na platformę Windows Mobile. W swojej dotych-czasowej pracy zajmował się również tworze-niem aplikacji w technologii Flash.Kontakt z autorem: [email protected]

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.

Page 23: SDJ Extra 34 Biblia
Page 24: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia24

Programowanie Windows MobileRAPI

www.sdjournal.org 25

RAPI (patrz również link [1]) – Remo-te API – to zestaw funkcji dla kom-puterów typu desktop umożliwiają-

cych operacje na plikach urządzeń przeno-śnych pracujących pod kontrolą Windows CE, dostęp do rejestru, baz danych, zdalne uruchomienie programu czy nawet wywo-łanie odpowiednio spreparowanej funkcji z biblioteki DLL urządzenia. Dzięki projek-towi SynCE ([2]) biblioteka RAPI stała się 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-

dziecie tu opis implementacji dwóch krót-kich programów dla Windows CE – pierw-szy wysyła SMS poprzez telefon wyposażony w Windows Mobile, drugi tworzy zrzut ekra-nu urządzenia mobilnego – będących chyba najbardziej reprezentatywnym – oprócz ko-piowania plików – przykładem wykorzysta-nia RAPI.

Instalacja

MS WindowsMicrosoft Windows Vista nie wymaga żad-nej specjalnej instalacji, aby mieć dostęp do funkcji RAPI. Wystarczy podłączyć telefon

przez kabel USB do komputera stacjonar-nego, najczęściej potwierdzić kilka mniej lub bardziej zrozumiałych pytań za pomo-cą OK, definiując niniejszym tzw. partner-stwo telefonu i komputera, i już za pomo-cą wbudowanego w system operacyjny eks-ploratora plików możemy przeciągać i upusz-czać pliki między komputerem i PDA czy, w zależności od konfiguracji, synchronizować pocztę i terminy między zainstalowanym na urządzeniu MS Outlookiem oraz zainstalo-wanym na komputerze stacjonarnym, jaka niespodzianka, również MS Outlookiem. Wszystkie te operacje pracują w oparciu o funkcje RAPI i jeśli np. nie mamy proble-mów z kopiowaniem plików za pomocą eks-ploratora, oznacza to, że nasz komputer z systemem MS Windows Vista w zupełno-ści sprosta wymaganiom przedstawionego w tym artykule kodu.

Inne systemy MS Windows wymagają in-stalacji programu MS Active Sync, z reguły dostarczanego wraz z telefonem na towarzy-szącej mu płytce CD.

Nieszczęśliwcy nie posiadający takiej płytki (lepiej niech się nie przyznają, skąd

RAPI

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

Dowiesz się:• Jak połączyć się z Windows CE i Mobile z po-

ziomu Windows i Linuksa;• Jak napisać i skompilować aplikację C wyko-

rzystującą RAPI;• Jak napisać i skompilować prostą aplika-

cję w C dla Windows CE i Mobile za pomocą kompilatora Microsoftu oraz cegcc.

Powinieneś wiedzieć:• Jak programować w C;• Jak posługiwać się kompilatorami Visual C i

GCC w środowisku Windows i Linux;• Przynajmniej w podstawowym zakresie, jak

pisać makefile-e dla nmake.exe oraz gmake;• Jak wygląda prosta aplikacja Win32.

Poziom trudności

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

Rysunek 1. Interfejs rndis0 do komunikacji z Windows CE

Page 25: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia24

Programowanie Windows MobileRAPI

www.sdjournal.org 25

mają telefon, lub który przedstawiciel han-dlowy sobie na nich zaoszczędził) mogą spróbować załadować program z [3], przy czym należy wyraźnie wspomnieć, że ser-wis Microsoftu dostępny pod tym adre-sem w czasie pisania artykułu wyraźnie fa-woryzuje Polaków, nie wymagając od nas, w przeciwieństwie do innych nacji, nawet zarejestrowania się, aby umożliwić ścią-gnięcie programu. Po instalacji MS Acti-ve Sync i zapewne po ponownym urucho-mieniu komputera podłączamy urządze-nie PDA, za pomocą klikania na OK i Dalej definiujemy znane już w MS Windows Vi-sta partnerstwo, po czym za pomocą wbu-dowanego w MS Active Sync eksploratora możemy zabrać się za przeciąganie i upusz-czanie plików między PDA i komputerem biurkowym.

LinuxInstalacja pod Linuksami to już nieco więk-sza przygoda. Pod adresem [4] szczegóło-wo opisano instalację dla różnych dystrybu-cji Linuksa oraz dla FreeBSD. Jako szczęśli-wy posiadacz OpenSuSE 11.1 mogłem sko-rzystać z gotowych pakietów RPM, przy czym zainstalować należy przynajmniej: ste-rownik usb-rndis-lite-*-samsung, synce-hal, libsynce0, libsynce-devel, librapi2, librapi-devel. Przydatne mogą być również rapi2-tools oraz źródła pakietu librapi2, które często będziemy używać jako ściągaw-ki. Nic nie stoi na przeszkodzie zainstalowa-nia pozostałych pakietów i wyposażenia sys-temu w przeróżne wtyczki do KDE, GNO-ME i bardziej znanych programów poczto-wych.

Komunikacja między PDA i komputerem stacjonarnym odbywa się za pomocą proto-kołu IP poprzez interfejs sieciowy rndis0. Aby to zadziałało, musimy przed podłącze-niem telefonu do komputera wykonać kilka istotnych czynności, porównajcie proszę rów-nież opis instalacji dla Waszego systemu pod adresem [4]. W przypadku mojego OpenSu-SE musiałem wykonać następujące kroki na komputerze biurkowym:

• usunąłem wszystkie pakiety instalacyj-ne Open Sync;

• zabroniłem ładowania się modu-łu ipaq, dodając blacklist ipaq do /etc/modprobe.d/blacklist, bez tego komu-nikacja IP między posiadanym przeze mnie HTC3300, znanym również jako T-Mobile MDA III, i Linuksem działa-ła tylko wtedy, kiedy na telefonie włą-czony był Internet sharing, co niemiło odzwierciedliło się w rachunku telefo-nicznym;

• zdefiniowałem uzyskiwanie adresu IP na interfejsie sieciowym rndis0 poprzez klienta DHCP.

Aby uzyskać adres IP poprzez klienta DHCP, wystarczy zwykle uruchomić pro-gram dhclient lub dhcpcd z nazwą inter-fejsu sieciowego jako jednym z parame-trów linii poleceń. Współczesne Linuksy preferują jednak zdefiniowanie takiego za-chowania w stosownym pliku konfigura-cyjnym, pozostawiając wywołanie klienta DHCP któremuś ze skryptów uruchamia-nych przez hald w momencie pojawienia się interfejsu rndis0, czyli w momencie podłączenia PDA do komputera przez ka-bel USB. W przypadku OpenSuSE należa-ło zdefiniować plik /etc/sysconfig/network/ifcfg-rndis0 z zawartością przedstawioną w Listingu 1.

Ostatecznie należało uaktywnić w telefo-nie tzw. rozszerzone funkcje sieciowe dla po-łączenia USB w ustawieniach połączeń pa-nelu sterowania, aby umożliwić komunika-cję IP oraz uaktywnić serwer DHCP telefo-nu (Settings/Connections/USB to PC/Enable advanced network functionality). Dopie-ro teraz możemy pozwolić sobie na podłą-czenie urządzenia do komputera za pomo-cą kabla USB, co powinno skutkować po-jawieniem się w systemie interfejsu siecio-wego rndis0 z przydzielonym mu adresem IP 169.254.2.2. Adres 169.254.2.1 należy do telefonu (Rysunek 1). Aby ostatecznie upewnić się, że wszystko działa jak należy, możemy uruchomić jeden z programów za-wartych w pakiecie rapi2-tools, np. pls /, psta-tus itd. (Rysunek 2).

Pierwszy programPo nacieszeniu się możliwościami progra-mów z rapi2-tools, MS Active Sync lub na-rzędziami dostępnymi pod MS Windows

Vista, zabierzmy się za napisanie pierw-szego prostego programu. Każdy program korzystający z RAPI powinien zaczynać się wywołaniem funkcji CeRapiInit() i koń-czyć wywołaniem CeRapiUninit(). Zada-niem naszego pierwszego programu bę-dzie przedstawienie zawartości pliku z te-lefonu na standardowym wyjściu (czy-li ekranie terminala lub konsoli cmd.exe) komputera biurkowego. W tym celu za pomocą funkcji CeCreateFile() otwie-ramy plik o podanej nazwie na telefonie i funkcją CeReadFile() wczytujemy pew-ne porcje bajtów do bufora i zapisujemy

Listing 1. /etc/sysconfig/network/ifcfg-rndis0

BOOTPROTO='dhcp'

BROADCAST=''

ETHTOOL_OPTIONS=''

IFPLUGD_PRIORITY='0'

IPADDR=''

MTU=''

NAME='RNDIS'

NETMASK=''

NETWORK=''

REMOTE_IPADDR=''

STARTMODE='ifplugd'

USERCONTROL='no'

Listing 2. Funkcja wyświetlająca zawartość pliku PDA na ekranie komputera biurkowego

#include <stdio.h>

#include <rapi.h>

void wcat(LPCWSTR aFileName) {

char buf[1024];

HRESULT hr;

HANDLE hf;

DWORD nread;

hr = CeRapiInit();

if (!FAILED(hr)) {

hf = CeCreateFile(aFileName,

GENERIC_READ,

0,

NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_

NORMAL,

0);

if (hf != INVALID_HANDLE_VALUE) {

while(CeReadFile(hf, buf, 1024,

&nread, NULL) &&

nread > 0)

fwrite(buf, 1, nread,

stdout);

fflush(stdout);

CeCloseHandle(hf);

}

CeRapiUninit();

} /* if (!FAILED(hr)) */

} /* wcat() */Rysunek 2. Wynik działania programu pstatus z pakietu rapi2-tools

Page 26: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia26

Programowanie Windows MobileRAPI

www.sdjournal.org 27

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

#include <stdio.h>

#include <rapi.h>

#ifdef _WIN32

typedef char t_ucs2le;

#else

# include <iconv.h>

# include <langinfo.h>

# include <locale.h>

# include <string.h>

typedef struct {

iconv_t from;

iconv_t to;

} t_ucs2le;

#endif

const WCHAR WCHAR0 = (WCHAR) 0;

static const size_t _WRONG = (size_t) -1;

#ifdef _WIN32

int ucs2le_open(t_ucs2le *theHandle) { return 0; }

void ucs2le_close(t_ucs2le *theHandle) { }

int ucs2le_from(const t_ucs2le *aHandle,

char *theString,

LPCWSTR aWStr,

size_t aMaxNum) {

if (wcstombs(theString, aWStr, aMaxNum) != _WRONG) {

theString[aMaxNum] = '\0';

return 0;

}

return -1;

}

int ucs2le_to(const t_ucs2le *aHandle,

LPWSTR theWString,

const char *anStr,

size_t aMaxNum) {

if (mbstowcs(theWString, anStr, aMaxNum) != _WRONG) {

theWString[aMaxNum] = WCHAR0;

return 0;

}

return -1;

}

#else

static const iconv_t _ICWRONG = (iconv_t) -1;

int ucs2le_open(t_ucs2le *theHandle) {

static const char UCS2LE[] = "UCS-2LE";

const char *curcp;

setlocale(LC_ALL, "");

curcp = nl_langinfo(CODESET);

if (curcp != NULL && *curcp != '\0') {

theHandle->from = iconv_open(curcp, UCS2LE);

if (theHandle->from != _ICWRONG) {

theHandle->to = iconv_open(UCS2LE, curcp);

if (theHandle->to != _ICWRONG) return 0;

}

}

return -1;

}

void ucs2le_close(t_ucs2le *aHandle) {

if (aHandle->from != _ICWRONG && aHandle->from != NULL)

iconv_close(aHandle->from);

if (aHandle->to != _ICWRONG && aHandle->to != NULL)

iconv_close(aHandle->to);

memset(aHandle, 0, sizeof(t_ucs2le));

}

static size_t _ucs2le_len(LPCWSTR aWStr) {

LPCWSTR ptr;

size_t retval;

retval = 0; for(ptr = aWStr; *ptr != WCHAR0; ptr++)

retval++;

return retval;

}

int ucs2le_from(const t_ucs2le *aHandle,

char *theString,

LPCWSTR aWStr,

size_t aMaxNum) {

iconv_t h;

size_t inbytesleft, outbytesleft, iret;

char *inbuf, *outbuf;

int retval;

retval = -1;

if ((h = aHandle->from) != NULL && h != _ICWRONG) {

inbytesleft = _ucs2le_len(aWStr);

if (aMaxNum < inbytesleft) inbytesleft = aMaxNum;

inbytesleft *= 2;

outbytesleft = aMaxNum;

inbuf = (char *) aWStr;

outbuf = theString;

do {

iret = iconv(h, &inbuf, &inbytesleft, &outbuf,

&outbytesleft);

} while (iret != _WRONG && inbytesleft > 0);

*outbuf = '\0';

if (inbytesleft <= 0) retval = 0;

}

return retval;

}

int ucs2le_to(const t_ucs2le *aHandle,

LPWSTR theWString,

const char *anStr,

size_t aMaxNum) {

iconv_t h;

size_t inbytesleft, outbytesleft, iret;

char *inbuf, *outbuf;

LPWSTR ptr;

int retval;

retval = -1;

if ((h = aHandle->to) != NULL && h != _ICWRONG) {

Page 27: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia26

Programowanie Windows MobileRAPI

www.sdjournal.org 27

zawartość bufora na standardowe wyj-ście (stdout) naszego programu aż napo-tkamy koniec pliku. Ostatecznie zamyka-my plik funkcją CeCloseHandle(), patrz Listing 2. Czytelnicy, którzy już otarli się o programowanie WINAPI, zapewne od razu zauważą, że program pisany dla biurkowej wersji MS Windows wyglądał-by niemal identycznie. Z przedstawione-go listingu należałoby jedynie usunąć wy-wołania CeRapiInit() i CeRapiUninit() oraz skorzystać z funkcji CreateFile(), ReadFile() i CloseHandle() zamiast z za-stosowanych funkcji z przedrostkiem Ce. Pozostałym czytelnikom należy się jednak kilka słów wyjaśnienia. DWORD, HRESULT i HANDLE to stosowane powszechnie w świe-cie MS Windows typy danych odpowiada-jące int32_t lub uint32_t. Pod MS Win-dows typy te zdefiniowane są w pliku na-główkowym WTypes.h, choć zalecane jest

raczej użycie pliku nagłówkowego Win-dows.h, w którym dołączenie do WTypes.h jest również zawarte. Pod systemami unik-sowymi wyposażonymi w librapi2 typy te zdefiniowane są w pliku nagłówkowym synce_types.h, należącym do pakietu lib-synce-devel i dołączanym w pliku nagłów-kowym rapi.h. Nieco więcej miejsca bę-dziemy musieli poświęcić typowi LPCWSTR, który pod MS Windows odpowiada typo-wi wskaźnikowemu const wchar_t *, czyli wskaźnikowi na łańcuch tzw. szero-kich znaków, zawierających w swoich ze-stawach narodowe litery wielu języków, absolutny obowiązek dla każdej nowocze-snej aplikacji wielojęzykowej. Biblioteka WINAPI dla systemów biurkowych niemal w całości pracuje w oparciu o szeroki ze-staw znaków, jeśli program skompilowano ze zdefiniowanym makrem UNICODE (opcja -DUNICODE kompilatora), wersje Windows

dla urządzeń przenośnych pracują wyłącz-nie w oparciu o wchar_t. Za pomocą funk-cji standardowej biblioteki C mbstowcs() i wcstombs() można zamienić łańcuch zna-ków char na wchar_t lub odwrotnie. Ani typ wchar_t, ani obydwie funkcje do kon-wersji nie są tworami specyficznymi dla WINAPI i są doskonale znane w świecie in-nych systemów operacyjnych. W zależno-ści od systemu typ wchar_t jest jednak róż-nie odzwierciedlany. Różnice zaczynają się już przy samym rozmiarze typu; o ile pod Linuksem są to 4 bajty, to pod MS Win-dows już tylko 2. Dalej różnice mogą doty-czyć kolejności bajtów – BIG i LITTLE EN-DIAN, nie wspominając już o samych stro-nach kodowych znaków. Jeśli pod np. Li-nuksem weźmiemy łańcuch znaków char w aktualnej stronie kodowej i przetłuma-czymy go za pomocą mbstowcs na łańcuch wchar_t, to ze 100% pewnością otrzyma-

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

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

inbytesleft = strlen(anStr);

if (aMaxNum < inbytesleft) inbytesleft = aMaxNum;

outbytesleft = aMaxNum * 2;

inbuf = (char *) anStr;

outbuf = (char *) theWString;

do {

iret = iconv(h, &inbuf, &inbytesleft, &outbuf,

&outbytesleft);

} while (iret != _WRONG && inbytesleft > 0);

ptr = (LPWSTR) outbuf;

*ptr = WCHAR0;

if (inbytesleft <= 0) retval = 0;

}

return retval;

}

#endif

void wcat(LPCWSTR aFileName) {

char buf[1024];

HANDLE hf;

DWORD nread;

hf = CeCreateFile(aFileName, GENERIC_READ, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,

0);

if (hf != INVALID_HANDLE_VALUE) {

while(CeReadFile(hf, buf, 1024, &nread, NULL) && nread

> 0)

fwrite(buf, 1, nread, stdout);

fflush(stdout);

CeCloseHandle(hf);

}

}

int main(int argc, char **argv) {

WCHAR wfn[FILENAME_MAX+1];

t_ucs2le hw;

HRESULT hr;

int retval, i;

memset(&hw, 0, sizeof(hw));

hr = CeRapiInit();

if (FAILED(hr)) { retval = -1; goto end; }

if ((retval = ucs2le_open(&hw)) != 0) goto end;

for(i = 1; i < argc; i++) {

if ((retval = ucs2le_to(&hw, wfn, argv[i], FILENAME_

MAX)) != 0) goto end;

wcat(wfn);

}

end:

ucs2le_close(&hw);

if (!FAILED(hr)) CeRapiUninit();

return retval;

}

@ECHO OFF

SET VC=%ProgramFiles%\Microsoft Visual Studio 9.0\VC

SET MSSDK=%ProgramFiles%\Microsoft SDKs\Windows\v6.1

SET ACTIVESYNC=%VC%\SmartDevices\SDK\ActiveSync

SET TARGETCPU=

SET INCLUDE=%VC%\include;%MSSDK%\include;%ACTIVESYNC%\

inc;%INCLUDE%

SET LIB=%VC%\lib;%MSSDK%\lib;%ACTIVESYNC%\lib;%LIB%

SET PATH=%VC%\bin;%VC%\..\Common7\IDE;%MSSDK%\

bin;%SystemRoot%\Microsoft.NET\

Framework\v2.0.50727;%PATH%

Page 28: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia28

Programowanie Windows MobileRAPI

www.sdjournal.org 29

ny łańcuch nie będzie mógł zostać zasto-sowany do wywoływanych za pomocą RA-PI funkcji Windows CE. Taki numer przej-dzie tylko wtedy, jeśli tworzymy i kompilu-jemy nasz program na biurkowej wersji MS Windows. To, co Microsoft nazywa szero-kim zestawem znaków, naprawdę jest stro-ną kodową UCS-2LE, na szczęście dosko-nale znaną dostępnej chyba na każdej plat-formie bibliotece GNU Iconv. Aby wywo-łać pod Linuksem przedstawioną na Listin-gu 2 funkcję wcat(), musimy zdefiniować nazwę pliku jako łańcuch znaków char, a następnie za pomocą funkcji iconv() za-mienić go z aktualnej strony kodowej sys-temu na łańcuch znaków w stronie kodo-wej UCS-2LE. Pod MS Windows możemy z powodzeniem zamiast iconv() wywołać standardową funkcję mbstowcs().

Na Listingu 3 przedstawiono nieco uproszczony kod programu wykorzystują-cego funkcję wcat() do wyświetlania za-wartości pliku. Poprzez odpytanie, czy zdefiniowano makro _WIN32, podzielono program na dwie części; jedną dla Micro-soft Windows i drugą dla pozostałych sys-temów operacyjnych, _WIN32 zdefiniowane jest standardowo w bodaj każdym kompila-torze C dla platformy Microsoftu. Funkcja ucs2le_open() przeprowadza inicjaliza-cję środowiska do tłumaczenia łańcuchów znaków z i na stronę kodową UCS-2LE. W wersji dla Windows funkcja ta nie musi ro-bić nic, ponieważ korzystamy ze standar-dowych narzędzi, nie wymagających żad-nych inicjalizacji. Choć definiowanie i ko-rzystanie z pustych funkcji może wydawać się nieco rozrzutne, to jednak bardzo nam to ułatwia kod. Poza tym w dobrze zorgani-zowanym programie funkcja ta będzie wy-wołana tylko raz i nie stracimy na nią zbyt wiele czasu. W wersji dla pozostałych sys-temów operacyjnych ucs2le_open() ładu-je za pomocą iconv_open() tablice kon-wersji z aktualnej strony kodowej systemu na UCS-2LE i odwrotnie. Stąd też różnice w typie danych t_ucs2le, który w wypad-ku wykorzystania biblioteki iconv zawie-ra dwa uchwyty do tablic kodowych, pod-czas gdy pod Windows może on być do-wolny, np. króciutki typ char, ponieważ i tak nie jest on tam wykorzystywany. Funk-cja ucs2le_close() zwalnia zasoby zare-zerwowane przez ucs2le_open(), czyli pod Windowsem jest to ponownie pusta i bezczynna funkcja, podczas gdy na pozo-stałych systemach operacyjnych wywoły-wana jest iconv_close(), zwalniająca pa-mięć zajmowaną przez tablice konwersji. Faktyczną zamianę kodowania znaków zrealizowano w funkcjach ucs2le_from() oraz ucs2le_to(). Dla Windows wykorzy-stano standardowe funkcje wcstombs() i mbstowcs(). Pozostałe systemy mozolnie

Listing 5. Fragment programu rapicat.c w wersji bez rapi.h i bez rapi.lib

#include <stdio.h>

#include <windows.h>

typedef HRESULT (*t_cerapiinit)(void);

typedef HRESULT (*t_cerapiuninit)(void);

typedef HANDLE (*t_cecreatefile)(LPCWSTR lpFileName,

DWORD dwDesiredAccess,

DWORD dwShareMode,

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

DWORD dwCreationDisposition,

DWORD dwFlagsAndAttributes,

HANDLE hTemplateFile);

typedef BOOL (*t_cereadfile)(HANDLE hFile,

LPVOID lpBuffer,

DWORD nNumberOfBytesToRead,

LPDWORD lpNumberOfBytesRead,

LPOVERLAPPED lpOverlapped);

typedef BOOL (*t_ceclosehandle)(HANDLE hObject);

t_cerapiinit pCeRapiInit;

t_cerapiuninit pCeRapiUninit;

t_cecreatefile pCeCreateFile;

t_cereadfile pCeReadFile;

t_ceclosehandle pCeCloseHandle;

void wcat(LPCWSTR aFileName) {

char buf[1024];

HANDLE hf;

DWORD nread;

hf = pCeCreateFile(aFileName, GENERIC_READ, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

if (hf != INVALID_HANDLE_VALUE) {

while(pCeReadFile(hf, buf, 1024, &nread, NULL) && nread > 0)

fwrite(buf, 1, nread, stdout);

fflush(stdout);

pCeCloseHandle(hf);

}

}

int main(int argc, char **argv) {

WCHAR wfn[FILENAME_MAX+1];

t_ucs2le hw;

HRESULT hr;

HMODULE hdll;

int retval, i;

memset(&hw, 0, sizeof(hw));

if ((hdll = LoadLibrary(TEXT("rapi.dll"))) != NULL) {

pCeRapiInit = (t_cerapiinit) GetProcAddress(hdll, TEXT("CeRapiInit"));

pCeRapiUninit = (t_cerapiuninit) GetProcAddress(hdll, TEXT("CeRapiUninit"));

pCeCreateFile = (t_cecreatefile) GetProcAddress(hdll, TEXT("CeCreateFile"));

pCeReadFile = (t_cereadfile) GetProcAddress(hdll, TEXT("CeReadFile"));

pCeCloseHandle = (t_ceclosehandle) GetProcAddress(hdll, TEXT("CeCloseHandle"));

memset(&hw, 0, sizeof(hw));

hr = pCeRapiInit();

if (FAILED(hr)) { retval = -1; goto end; }

if ((retval = ucs2le_open(&hw)) != 0) goto end;

for(i = 1; i < argc; i++) {

if ((retval = ucs2le_to(&hw, wfn, argv[i], FILENAME_MAX)) != 0) goto end;

wcat(wfn);

}

end:

ucs2le_close(&hw);

if (!FAILED(hr)) pCeRapiUninit();

FreeLibrary(hdll);

}

Page 29: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia28

Programowanie Windows MobileRAPI

www.sdjournal.org 29

wywołują funkcję iconv() aż do przekon-wertowania całego łańcucha znaków lub wyczerpania się przewidzianego dla doce-lowego łańcucha rozmiaru bufora. W funk-cji main() dla każdej z nazw plików poda-nej w linii komend w aktualnej stronie ko-dowej systemu dokonywana jest konwer-sja na stronę kodową UCS-2LE, której wy-nik umieszczany jest w buforze wfn. Na-stępnie dla nazwy pliku z wfn wywoływa-na jest znana nam już funkcja wcat(). Pro-szę zwrócić uwagę, że funkcja wcat() zo-stała nieco zmodyfikowana, wywołania do CeRapiInit() oraz CeRapiUninit() prze-niesiono do funkcji main(). Omawiany program może zostać wywołany z wieloma parametrami linii poleceń, czyli dla wie-lu plików. Wywołując wcat() dla każdego z plików, bez tej zmiany za każdym razem dokonywalibyśmy inicjalizacji i zwalniania środowiska RAPI, co prawdopodobnie nie pozostałoby bez wpływu na szybkość dzia-łania programu.

Przedstawione na listingach przykłady są z racji ograniczonego miejsca mocno okrojo-nymi i często zmienionymi wersjami progra-mów dostępnych na stronie domowej opisy-wanego projektu [0]. W razie wątpliwości uprzejmie proszę brać pod uwagę kod źró-dłowy ze strony projektu, a nie przedstawio-ne okrojone przykłady.

KompilacjaKompilacja pod Linuksem jest bajecznie pro-sta i sprowadza się do wywołania

gcc -o rapicat rapicat.c -lrapi

zakładając, że źródła programu z Listingu 3 zapisaliśmy w pliku rapicat.c.

Kompilacja pod MS Windows mogłaby być niemal równie prosta:

cl.exe -D_CRT_SECURE_NO_DEPRECATE

rapicat.c rapi.lib

jeśli spełnione zostałyby następujące warunki:

• posiadamy MS Visual Studio w wersji Standard lub droższej z zainstalowanym Windows Mobile SDK;

• mamy dobrze poustawiane ścieżki w zmiennych środowiskowych PATH, LIB oraz INCLUDE.

Ostatni warunek jest z reguły spełniony, jeśli korzystamy z okna konsoli MS Vi-sual Studio, w razie wątpliwości może-my skorzystać ze skryptu przedstawione-go na Listingu 4. Skrypt należy wywołać w oknie cmd.exe, zanim zaczniemy wywo-ływać kompilator cl.exe, program konsoli-dujący link.exe czy interpreter plików ma-ke nmake.exe. W zależności od posiadanej

Listing 5 cd. Fragment programu rapicat.c w wersji bez rapi.h i bez rapi.lib

else {

fprintf(stderr, "Can't find rapi.dll\n");

retval = -1;

}

return retval;

}

Listing 6. Funkcja kopiująca plik z PDA na komputer stacjonarny

#include <stdio.h>

#include <rapi.h>

void wget(LPCWSTR aPDAFileName, const char *aLocFileName) {

char buf[1024];

FILE *f;

HRESULT hr;

HANDLE hf;

DWORD nread;

hr = CeRapiInit();

if (!FAILED(hr)) {

hf = CeCreateFile(aPDAFileName, GENERIC_READ, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

if (hf != INVALID_HANDLE_VALUE) {

f = fopen(aLocFileName, "wb");

if (f != NULL) {

while(CeReadFile(hf, buf, 1024, &nread, NULL) && nread > 0)

fwrite(buf, 1, nread, f);

fclose(f);

}

CeCloseHandle(hf);

}

CeRapiUninit();

}

} /* wget() */

Listing 7. Funkcja kopiująca plik z komputera stacjonarnego na PDA

#include <stdio.h>

#include <rapi.h>

void wput(LPCWSTR aPDAFileName, const char *aLocFileName) {

char buf[1024];

FILE *f;

HANDLE hf;

size_t nread;

DWORD nwritten, remaining;

const char *ptr;

if ((f = fopen(aLocFileName, "rb")) != NULL) {

hf = CeCreateFile(aPDAFileName, GENERIC_WRITE, 0, NULL,

CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

if (hf != INVALID_HANDLE_VALUE) {

while(!feof(f) && (nread = fread(buf, 1, 1024, f)) > 0) {

ptr = buf;

remaining = (DWORD) nread;

while(remaining > 0 && CeWriteFile(hf, ptr, remaining, &nwritten, NULL)) {

remaining -= nwritten;

ptr += nwritten;

} /* while */

} /* while */

CeCloseHandle(hf);

}

fclose(f);

}

} /* wput() */

Page 30: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia30

Programowanie Windows MobileRAPI

www.sdjournal.org 31

wersji i sposobu instalacji MS Visual Stu-dio konieczne mogą być drobne zmiany w skrypcie.

Dużo trudniej jest spełnić warunek pierwszy. MS Visual Studio Standard Edi-tion to pakiet komercyjny, wcale nie tani i – delikatnie mówiąc – nieco zbyt duży i skomplikowany jak na nasze skromne po-trzeby skompilowania małej aplikacji kon-solowej. Wprawdzie pod adresem [8] moż-na ściągnąć bezpłatnie ok. 1.5GB obraz płyty instalacyjnej DVD zawierającej za-równo najnowszy kompilator MS Visual C, jak i niezbędny Microsoft SDK, brak w nim jednak narzędzi dla urządzeń mobil-nych, w tym plików rapi.h oraz rapi.lib. Pli-ki te zawarte są w pakietach Windows Mo-bile SDK, np. Windows Mobile 6 SDK Re-fresh.msi, również bezpłatnie dostępnym w strefie download Microsoftu, który in-staluje się tylko wtedy, kiedy mamy już zainstalowaną odpowiednią płatną wer-sję MS Visual Studio. Można wprawdzie pokusić się o rozpakowanie pliku insta-lacyjnego MSI za pomocą np. programu dark.exe zawartego w pakiecie WiX [6], ale zdaje się, że otrzemy się wtedy o grani-cę legalności.

Dodatkowo pliki rapi.lib oraz rapi.h zo-stały stworzone z myślą o kompilatorze i środowisku programistycznym Microso-ftu i wcale nie jest pewne, że uda nam się 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-

my ją zwolnić za pomocą funkcji Win32 FreeLibrary. Fragment zmienionego pro-gramu rapicat.c, wykorzystującego wskaź-

niki do funkcji, przedstawiono na Listingu 5. Typy t_cerapiinit, t_cerapiuninit, t_cecreatefile, t_cereadfile oraz t_

Listing 8. Specjalne katalogi na PDA

#include <stdio.h>

#include <rapi.h>

int main() {

t_ucs2le ucs2le;

WCHAR wpath[MAX_PATH+1];

char path[MAX_PATH+1];

HRESULT hr;

size_t i;

if (ucs2le_open(&ucs2le) == 0) {

hr = CeRapiInit();

if (!FAILED(hr)) {

for (i = 0; i < 64; i++) {

path[0] = '\0';

if (CeGetSpecialFolderPath(i, MAX_PATH, wpath)) {

wpath[MAX_PATH] = 0;

if (ucs2le_from(&ucs2le, path, wpath, MAX_PATH) == 0)

fprintf(stdout, "%04x %s\n", i, path);

}

}

CeRapiUninit();

}

ucs2le_close(&ucs2le);

}

return 0;

}

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

Page 31: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia30

Programowanie Windows MobileRAPI

www.sdjournal.org 31

ceclosehandle to typy wskaźników na funkcje, odpowiadające de facto defini-cjom samych funkcji RAPI, które moż-na znaleźć w nieużywanym teraz przez nas pliku nagłówkowym rapi.h. Do stwo-rzenia takich definicji z braku tego pliku pod Windowsem możemy posłużyć się albo dokumentacją MSDN [1], albo pli-kiem rapi.h zawartym w pakiecie libra-pi2 [2]. Zamiast funkcji CeRapiInit(), CeRapiUninit(), CeCreateFile() itd. używamy zmiennych wskaźnikowych, od-

powiednio pCeRapiInit, pCeRapiUninit, pCeCreateFile, inicjalizowanych w funkcji main() za pomocą wywołań do GetProcAddress(). Taki program może-my już skompilować w dużo prostszy spo-sób, używając

cl.exe -D_CRT_SECURE_NO_DEPRECATE rapicat.c

które to wywołanie bez problemu działa również na bezpłatnych wersjach kompilato-rów Microsoftu.

Zamiast 1.5GB wersji MS Windows SDK możemy skorzystać ze zgrabnego kompila-tora gcc zawartego w pakiecie MinGW [5]. Minimalna instalacja MinGW polega na ściągnięciu pakietów gcc, binutils, Win32 API oraz opcjonalnie make, rozpakowa-niu ich do jednego katalogu i ustawieniu ścieżki poszukiwań na podkatalog bin. Po-tem możemy nasz program skompilować, używając:

gcc.exe -o rapicat.exe rapicat.c

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

#include <stdio.h>

#include <string.h>

#include <rapi.h>

static void _print_listfile(const t_ucs2le *ucs2le, const

CE_FIND_DATA *anEntry) {

long long int fsiz;

size_t fsiz1;

DWORD flags;

char at[11], filename[MAX_PATH+1];

if (anEntry != NULL)

strncpy(at, "----------", 10);

flags = anEntry->dwFileAttributes;

if ((flags & FILE_ATTRIBUTE_ARCHIVE) != 0) at[0] = 'A';

if ((flags & FILE_ATTRIBUTE_COMPRESSED) != 0) at[1] =

'C';

if ((flags & FILE_ATTRIBUTE_DIRECTORY) != 0) at[2] = 'D';

if ((flags & FILE_ATTRIBUTE_HAS_CHILDREN) != 0) at[3] =

'+';

if ((flags & FILE_ATTRIBUTE_HIDDEN) != 0) at[4] = 'H';

if ((flags & FILE_ATTRIBUTE_INROM) != 0) at[5] = 'O';

if ((flags & FILE_ATTRIBUTE_NORMAL) != 0) at[6] = 'N';

if ((flags & FILE_ATTRIBUTE_READONLY) != 0) at[7] = 'R';

if ((flags & FILE_ATTRIBUTE_SYSTEM) != 0) at[8] = 'S';

if ((flags & FILE_ATTRIBUTE_TEMPORARY) != 0) at[9] = 'T';

at[10] = '\0';

fsiz = anEntry->nFileSizeHigh;

fsiz <<= 32;

fsiz |= anEntry->nFileSizeLow;

if(ucs2le_from(ucs2le, filename, anEntry->cFileName,

MAX_PATH) == 0) {

fsiz1 = (size_t) fsiz;

fprintf(stdout, "%s %10ld %s\n", at, fsiz1, filename);

}

}

}

static void _ls(const t_ucs2le *ucs2le, const char *aPath) {

static const DWORD FAF = 0x00ff;

WCHAR wpath[MAX_PATH+1];

char path[MAX_PATH+1];

CE_FIND_DATA *list;

const CE_FIND_DATA *ptr;

size_t len;

DWORD num, i;

char c;

num = 0;

list = NULL;

if (aPath == NULL || (len = strlen(aPath)) == 0)

strncpy(path, "*", MAX_PATH);

else {

c = aPath[len-1];

if (c == '/' || c == '\\') {

strncpy(path, aPath, MAX_PATH-1); strcat(path, "*");

}

else if (c != '*' &&

ucs2le_to(ucs2le, wpath, aPath, MAX_PATH) ==

0 &&

(CeGetFileAttributes(wpath) & FILE_ATTRIBUTE_

DIRECTORY) != 0) {

strncpy(path, aPath, MAX_PATH-2); strcat(path, "\\*");

}

else strncpy(path, aPath, MAX_PATH);

}

path[MAX_PATH] = '\0';

if (ucs2le_to(ucs2le, wpath, path, MAX_PATH) == 0 &&

CeFindAllFiles(wpath, FAF, &num, &list) && list !=

NULL)

for(i = 0, ptr = list; i < num; i++, ptr++) _print_

listfile(ucs2le, ptr);

CeRapiFreeBuffer(list);

}

int main(int argc, char **argv) {

t_ucs2le ucs2le;

HRESULT hr;

int i;

if (ucs2le_open(&ucs2le) == 0) {

hr = CeRapiInit();

if (!FAILED(hr)) {

for(i = 1; i < argc; i++) _ls(&ucs2le, argv[i]);

CeRapiUninit();

}

ucs2le_close(&ucs2le);

}

return 0;

}

Page 32: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia32

Programowanie Windows MobileRAPI

www.sdjournal.org 33

Aby nie marnować zbytnio miejsca i nie komplikować opisu, pozwolę sobie dalsze przykłady przedstawić w wersji bez dy-namicznie ładowanych funkcji, czyli tak, jak byśmy mieli zarówno plik nagłówko-wy rapi.h, jak i niezbędną dla MS Visual C bibliotekę rapi.lib. Kod źródłowy do ar-tykułu, dostępny pod adresem [0] , może być jednak kompilowany na obydwa sposo-by, w zależności od zdefiniowania makra RAPI _ SDK (opcja kompilatora -DRAPI _

SDK) podczas kompilacji. Ciekawych odsy-

łam do źródeł modułu rapiwrap.c i rapiw-rap.h.

Działanie programu możemy sprawdzić, wywołując np. rapicat "/Windows/Menu Start/Programs/Python.lnk" lub innego pli-ku. W dalszej części artykułu dowiemy się również, jak wyświetlić zawartość katalogu Windows CE.

Dopóki się tego nie nauczymy, musimy posłużyć się programem pls z pakietu ra-pi2-tools lub eksploratorem Active Sync pod Windows w celu znalezienia ciekawszych

plików do obsłużenia za pomocą programu rapicat.

Kolejne programyJeśli potrafimy wyświetlić zawartość pli-ku z PDA na komputerze stacjonarnym, to również potrafimy skopiować ten plik. Wy-starczy w tym celu wywołać rapicat plik_pda > plik_lokalny. Dużo korzystniej by-łoby jednak zastąpić stałą stdout zmienną typu FILE*, która reprezentowałaby plik otwarty za pomocą funkcji fopen(). Li-sting 6 przedstawia funkcję wget() – pro-szę nie mylić ze znanym programem GNU – do kopiowania pliku z PDA na komputer stacjonarny.

W ramach ćwiczenia możecie spró-bować znaleźć 5 szczegółów różniących funkcję wget() od przedstawionej na Li-stingu 2 funkcji wcat(). Program do ko-piowania pliku z komputera stacjonarne-go na PDA przedstawiono na Listingu 7. Podstawowa różnica w stosunku do po-przedniego programu to inne parametry przekazane do funkcji CeCreateFile(): GENERIC_WRITE i CREATE_ALWAYS zamiast GENERIC_READ i OPEN_EXISTING, oraz uży-cie funkcji CeWriteFile() do zapisu da-nych na PDA zamiast CeReadFile() do ich odczytu. Za pomocą funkcji RAPI

CeDeleteFile(LPCWSTR *aPDAFileName) można skasować plik na PDA. Parametr aPDAFileName zarówno funkcji wput(), wget(), jak i CeDeleteFile() to wskaźnik na łańcuch znaków w omawianej już stro-nie kodowej UCS-2LE, który można utwo-rzyć za pomocą przedstawionej na Listingu 2 funkcji ucs2le_to().

Listing 8 przedstawia zastosowanie funk-cji CeGetSpecialFolderPath() zwracającej nazwy specjalnych ścieżek dostępnych na PDA. Identyfikatorami specjalnych ścieżek są stałe takie jak CSIDL_PROGRAMS, CSIDL_PERSONAL, CSIDL_STARTUP czy CSIDL_

RECENT zdefiniowane w rapi.h (librapi2) lub ShlObj.h (Windows, ten plik nagłówko-wy dołączany jest również do windows.h), w programie zamiast stałych zastosowano po prostu pętlę poprzez wszystkie ewentu-alnie możliwe wartości takich identyfikato-rów. Wynik działania programu dla niemiec-kiej wersji Windows Mobile przedstawiono na Rysunku 3.

Funkcja CeFindAllFiles(LPCWSTR dir, 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-la zmienna wskazywana przez num, przy czym po wywołaniu funkcji znajduje się w niej dokładna liczba elementów zwró-conych w contents. Parametr flags mo-że zawierać połączone logicznym "lub" sta-

Listing 10. Zdalne uruchomienie programu na PDA

#include <rapi.h>

#include <stdio.h>

#include <string.h>

#include "ucs2le.h"

int main(int argc, char **argv) {

WCHAR wprogname[MAX_PATH+1], wparams[512];

t_ucs2le ucs2le;

PROCESS_INFORMATION info;

HRESULT hr;

const WCHAR *pwparams;

int retval;

short isok;

retval = -1;

memset(&info, 0, sizeof(PROCESS_INFORMATION));

if (ucs2le_open(&ucs2le) == 0) {

if (argc > 1) {

if (ucs2le_to(&ucs2le, wprogname, argv[1], MAX_PATH) == 0) {

isok = 0;

if (argc > 2) {

if (ucs2le_to(&ucs2le, wparams, argv[2], 511) == 0) {

pwparams = wparams;

isok = 1;

}

}

else {

pwparams = NULL;

isok = 1;

}

if (isok) {

hr = CeRapiInit();

if (!FAILED(hr)) {

if (CeCreateProcess(wprogname, pwparams,

NULL, NULL, FALSE, 0,

NULL, NULL, NULL,

&info)) {

if (CeCloseHandle != NULL) {

CeCloseHandle(info.hProcess); CeCloseHandle(info.hThread);

}

}

CeRapiUninit();

}

}

}

}

else

}

return retval;

}

Page 33: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia32

Programowanie Windows MobileRAPI

www.sdjournal.org 33

łe FAF_ATTRIBUTES, FAF_CREATION_TIME, FAF_LASTACCESS_TIME itd., patrz również definicje w pliku nagłówkowym rapi.h (li-brapi2) lub rapitypes.h (Windows), dzięki którym możemy ograniczyć ilość zwraca-nych informacji, wartość 255 umieszczona w parametrze powoduje zwrócenie wszyst-kich dostępnych informacji. Parametr dir to ścieżka do pliku lub katalogu, o którym chcemy otrzymać informacje. Proszę pa-miętać, że jeśli parametr dir będzie zawie-rał nazwę katalogu bez znaku gwiazdki na końcu, to funkcja zwróci informacje tylko o tym katalogu, nie zaś o jego zawartości. Parametr contents to wskaźnik na tabli-cę elementów typu CE_FIND_DATA, do któ-rego funkcja zwraca informacje o znajdu-jących się w katalogu plikach i podkatalo-gach. Poniżej przedstawiono co ciekawsze pola struktury CE_FIND_DATA:

• dFileName to nazwa pliku lub podkata-logu, zakodowane rzecz jasna jako UCS-2LE.

• nFileSizeHigh, nFileSizeLow to wiel-kość pliku w bajtach. Obydwa pola to liczby typu int (DWORD w nomenklatu-rze Microsoftu), które razem symulu-ją typ 64-o bitowy, pozwalający przed-stawić w dniu dzisiejszym niewyobra-ż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, FIL E _ 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-

Listing 11. Typy danych oraz typy wskaźników do funkcji stosowanych do wysłania SMS z Windows Mobile

struct _sms_address {

int type;

TCHAR address[256];

};

struct _provider_specific {

DWORD options;

int class;

BYTE not_needed[680];

};

typedef

HRESULT (*t_SmsOpen)(LPCTSTR protocol,

DWORD mode,

DWORD *smsHandle,

HANDLE *not_used);

typedef

HRESULT (*t_SmsSendMessage)(DWORD smsHandle,

const struct _sms_address *smscAddress,

const struct _sms_address *destAddress,

const SYSTEMTIME *validity,

const BYTE *data,

DWORD datasize,

const BYTE *provider,

DWORD providersize,

int encoding,

DWORD options,

DWORD *msgid);

typedef HRESULT (*t_SmsClose)(DWORD smsHandle);

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.

Page 34: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia34

Programowanie Windows MobileRAPI

www.sdjournal.org 35

Time), ostatniego dostępu do pliku (ftLastAccessTime) oraz ostatniej modyfikacji pliku (ftLastWriteTime). Windowsowy typ FILETIME to ilość na-nosekund, która upłynęła od 1 stycz-nia 1601 roku, przedstawiona jako dwie liczby typu int dwHighDateTime i dwLowDateTime symulujące razem liczbę 64-o bitową. Zwykły czas ty-pu time _ t (ilość sekund od 1 stycz-nia 1970) możemy otrzymać, wykonu-jąc (((((long long) dwHighDateTime) << 32) | dwLowDateTime) -

116444736000000000LL) / 10000000, gdzie 116444736000000000 to, jak ła-two się domyślić, ilość nanosekund między 1 stycznia 1601 i 1 stycznia 1970. Programiści API Win32 mo-gą posłużyć się w zamian funkcją FileTimeToSystemTime().

Przykładowy program wyświetlający zawar-tość katalogu na standardowym wyjściu przedstawiono na Listingu 9, na Rysunku 4 przedstawiono zaś wynik jego działania pod Windows Vista.

Kolejne zastosowanie RAPI to odczyt oraz manipulacja rejestrem podłączonego do kom-putera urządzenia przenośnego. Doskona-łą kopalnią wiedzy o używaniu funkcji reje-stru są źródła dostępnego w ramach pakie-tu librapi2 [2] programu synce-registry. Za-awansowani programiści API Win32 szybko odnajdą tam funkcje znane z programowa-nia wyposażonych w Windows komputerów biurkowych, tyle że w nazwach funkcji znaj-duje się przedrostek Ce. Ponieważ listingi pro-gramów korzystających z rejestrów są z natu-ry rzeczy duże, zamiast namawiać wydawnic-two na zadrukowywanie płacht papieru nie-zrozumiałym kodem, postanowiłem odesłać zainteresowanych do wspomnianych źródeł synce-registry lub do implementacji funkcji _reg() i _reg1() znajdujących się w module rapi/rapi.c źródeł omawianego programu do-stępnych pod adresem [0].

Pierwszym krokiem w dostępie do re-jestru jest otwarcie klucza za pomocą CeRegOpenKeyEx(), np.

HKEY key;

CeRegOpenKeyEx(HKEY_LOCAL_MACHINE,

TEXT("\Software\Microsoft"), 0, 0,

&key);

HKEY _ LOCAL _ MACHINE, podobnie jak HKEY _CURRENT _ USER czy HKEY _ CLASSES _ ROOT, to nazwy tzw. kluczy root różnych części reje-stru, użytkownikom Windows zapewne zna-ne jako skróty HKLM, HKCU czy HKCR.

\Software\Microsoft to nazwa klucza w części rejestru, oczywiście zakodowana jako UCS-2LE. W zmiennej key funkcja zwraca uchwyt klucza, w powyższym przykładzie jest to klucz HKLM\Software\Microsoft. Funkcja CeReqQueryInfoKey() zwraca bliższe infor-macje na temat klucza: nazwy wszystkich wartości oraz nazwy wszystkich podkluczy. CeRegEnumValue() odczytuje wartość klu-cza o podanej nazwie. Wszystkie podklucze możemy potraktować rekurencyjnie poprzez ponowne wywołanie CeRegOpenKeyEx() i CeReqQueryInfoKey() dla tego samego klu-cza root i nazwy pełnej ścieżki podklucza (np. \Software\Microsoft\Windows CE Services, zakładając, że funkcja zwróciła nazwę pod-klucza Windows CE Services).

Po skorzystaniu z klucza, czyli po sko-rzystaniu z funkcji CeRegOpenKeyEx() i CeReqQueryInfoKey(), należy zamknąć klucz funkcją CeRegCloseKey().

Funkcja CeCreateProcess() uruchamia zdalnie program na PDA. Pierwszym parame-trem funkcji jest nazwa lub ścieżka uruchamia-nego programu, drugim parametrem są ewen-tualne parametry linii poleceń. Programy Win-dows, w odróżnieniu od zwykłych programów C uruchamianych za pomocą funkcji main(), otrzymują wszystkie parametry jako jeden łań-cuch znaków, a nie jako tablicę stringów.

Listing 12. Funkcja wysyłająca SMS pod podany numer

HRESULT sendsms(const TCHAR *aNumber, const TCHAR *aMessage) {

struct _sms_address number;

struct _provider_specific p;

t_SmsOpen pSmsOpen;

t_SmsSendMessage pSmsSendMessage;

t_SmsClose pSmsClose;

HMODULE hdll;

DWORD hsms, id;

HRESULT retval;

retval = S_FALSE;

hdll = NULL;

if (aNumber == NULL || aMessage == NULL) { retval = E_INVALIDARG; goto err; }

hdll = LoadLibrary(TEXT("sms.dll"));

if (hdll == NULL) { retval = E_HANDLE; goto err; }

pSmsOpen = (t_SmsOpen) GetProcAddress(hdll, TEXT("SmsOpen"));

if (pSmsOpen == NULL) { retval = E_FAIL; goto err; }

pSmsSendMessage = (t_SmsSendMessage) GetProcAddress(hdll,

TEXT("SmsSendMessage"));

if (pSmsSendMessage == NULL) { retval = E_FAIL; goto err; }

pSmsClose = (t_SmsClose) GetProcAddress(hdll, TEXT("SmsClose"));

if (pSmsClose == NULL) { retval = E_FAIL; goto err; }

retval = pSmsOpen(TEXT("Microsoft Text SMS Protocol"), 2, &hsms, NULL);

if (retval != S_OK) goto err;

memset(&number, 0, sizeof(struct _sms_address));

number.type = 1; /* International number type */

wcsncpy(number.address, aNumber, 255); number.address[256] = TEXT('\0');

memset(&p, 0, sizeof(struct _provider_specific));

p.options = 2; /* status report */

p.class = 1; /* message class */

retval = pSmsSendMessage(hsms,

NULL, /* default SMS center */

&number,

NULL,

(PBYTE) aMessage,

wcslen(aMessage) * sizeof(TCHAR),

(PBYTE) &p,

sizeof(struct _provider_specific),

0,

0,

&id);

if (retval != S_OK) { pSmsClose(hsms); goto err; }

retval = pSmsClose(hsms);

err:

if (hdll != NULL) FreeLibrary(hdll);

return retval;

}

Page 35: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia34

Programowanie Windows MobileRAPI

www.sdjournal.org 35

Ostatni parametr używany jest do otrzymania deskryptorów uruchomio-nego procesu i wątku, które za pomo-cą CeCloseHandle() należy po skorzysta-niu z funkcji CeCreateProcess() pozamy-kać. Listing 10 przedstawia program, któ-rego pierwszy parametr linii poleceń ozna-cza nazwę programu na PDA, drugi para-metr linii poleceń przekazywany jest nato-miast do uruchomionego programu jako je-go parametry.

Praktyczny przykład zastosowania zdal-nego uruchomienia procesu na PDA przedstawiono w skrypcie shell-owym synce-install-cab dostarczanym wraz z pakietem rapi2-tools lub znajdującym się wśród źródeł librapi2, służącym do insta-lacji tzw. plików typu cabinet na urządze-niu przenośnym z poziomu komputera biurkowego.

Skrypt ten kopiuje plik instalacyjny ca-binet za pomocą programu pcp (działające-go podobnie do tego przedstawionego na Listingu 7) do pliku /Windows/AppMgr/Install/synce-install.cab i zdalnie uruchamia za pomocą prun – podobnego do opisywa-nego tu programu z Listingu 10 – aplikację Windows CE wceload.exe. Podobnie zdalne uruchomienie unload.exe z nazwą pakietu jako parametrem linii poleceń spowoduje je-go odinstalowanie (spójrz również na skrypt synce-remove-program). Listę zainstalowa-nych na urządzeniu programów możemy znaleźć, przeglądając zawartość klucza reje-stru HKLM, Software\Apps.

SMS z komputera PCWiemy już, jak uruchomić zdalnie program na urządzeniu przenośnym z poziomu kom-putera PC (Listing 10), choć należy przy-znać, że ilość programów nadających się do zdalnego uruchamiania nie jest aż taka oszałamiająca. Spróbujmy uzupełnić te bra-ki dwoma własnymi aplikacjami i zacznijmy od programu wysyłającego SMS pod telefon i z treścią podanymi w linii poleceń.

SMS można wysłać za pomocą funkcji SmsSendMessage(), poprzedzając jej wywo-łanie funkcją SmsOpen() oraz kończąc ca-ły proces wywołaniem funkcji SmsClose(). Funkcje te znajdują się w zainstalowanej bo-daj na każdym telefonie z Windows Mobi-le bibliotece dynamicznej sms.dll, defini-cje moglibyśmy znaleźć w pliku nagłówko-wym sms.h, który wraz z plikiem sms.lib uży-wany jest do kompilacji programu za pomo-cą Microsoft Visual Studio wyposażonego w Windows Mobile SDK. Zanim zdecydujecie się na wykorzystanie tych dwóch plików, pa-miętajcie, że po pierwsze potrzebowalibyśmy płatną wersję MS Visual Studio w wersji przy-najmniej Standard, po drugie raczej dozgon-nie związalibyśmy się z kompilatorem i zesta-wem plików nagłówkowych Microsoftu.

Oczywiście nie ma nic w tym złego, szczę-śliwi użytkownicy oprogramowania Micro-softu mogą z powodzeniem przerwać czy-tanie rozdziału w tym miejscu i spojrzeć na dostarczony wraz z MS Windows Mobi-le SDK plik przykładowy Samples/Common/cpp/Win32/CellCore/sms/HelloSMS/main.cpp (zawarty przynajmniej w MS Windows Mo-bile 6 SDK Refresh) oraz rzecz jasna na do-kumentację MSDN funkcji.

Wszystkim pozostałym chciałbym zapropo-nować rozwiązanie bazujące na dynamicznym ładowaniu wspomnianych trzech funkcji za

pomocą LoadLibrary() i GetProcAddress() oraz na samodzielnym i mocno uproszczo-nym zdefiniowaniu typów danych wymaga-nych przez SmsSendMessage(), patrz Listing 11. Pierwszym niezbędnym typem zmien-nej jest struktura _sms_address, określają-ca w polu address numer telefonu, pod któ-ry chcemy wysłać SMS. Najrozsądniejszą war-tością dla pola type wydaje się 1, oznacza-jąca międzynarodowy numer telefonu (np. +48123456789). Drugim typem jest struk-tura _provider_specific, określająca typ wiadomości w polu class (1 dla SMS). Pole

Listing 13. Program wykorzystujący funkcję sendsms()

int WINAPI WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPWSTR szCmdLine,

int iCmdShow) {

TCHAR number[256];

const TCHAR *cp;

TCHAR *p;

int i;

for(i = 0, cp = szCmdLine, p = number;

i < 255 &&

*cp != TEXT('\0') &&

*cp != TEXT(' ') &&

*cp != TEXT('\t');

i++, cp++, p++)

*p = *cp;

*p = TEXT('\0');

if (*cp != TEXT('\0')) cp++;

sendsms(number, cp);

return 0;

} /* WinMain() */

Listing 14. Kompilacja programu do wysyłania SMS za pomocą CeGCC

/opt/cegcc/bin/arm-wince-cegcc-gcc \

-DUNDER_CE -D_WIN32_IE=0x0400 \

-o sms.exe sendsms.c smsapp.c

Listing 15. Kompilacja programu do wysyłania SMS za pomocą Embedded Visual C++ 4

clarm.exe -nologo -c -D_UNICODE -DUNICODE -DARM -D__ARM__ -DUNDER_CE=420 -D_WIN32_

WCE=420 -DWCE_PLATFORM_STANDARDSDK sendsms.c

clarm.exe -nologo -c -D_UNICODE -DUNICODE -DARM -D__ARM__ -DUNDER_CE=420 -D_WIN32_

WCE=420 -DWCE_PLATFORM_STANDARDSDK smsapp.c

link.exe -nologo -subsystem:WINDOWSCE -machine:THUMB -out:sms.exe smsapp.obj

sendsms.obj

Listing 16. Zmienne środowiskowe dla kompilatora Embedded Visual C++ 4

SET EVC=%ProgramFiles%\Microsoft eMbedded C++ 4.0

SET TARGETCPU=ARMV4I

SET EVCSDK=%ProgramFiles%\Windows CE Tools\wce400\STANDARDSDK

SET INCLUDE=%EVCSDK%\include\%TARGETCPU%;%EVCSDK%\MFC\include;%EVSDK%\ATL\

include;%INCLUDE%

SET LIB=%EVCSDK%\lib\%TARGETCPU%;%EVCSDK%\MFC\lib\%TARGETCPU%;%EVCSDK%\ATL\lib\

%TARGETCPU%;%LIB%

SET PATH=%EVC%\Common\EVC\Bin;%EVC%\EVC\WCE400\BIN;%PATH%

Page 36: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia36

Programowanie Windows MobileRAPI

www.sdjournal.org 37

options, jeśli zawiera wartość 2, powoduje, że otrzymamy raport o dostarczeniu SMS-a.

Pozostałe pola struktury, kompletnie niecie-kawe z punktu widzenia naszego skromnego

zadania, zostały na listingu przedstawione ja-ko tablica o nazwie not_needed i rozmiarze ta-kim, jak suma rozmiarów reszty pól oryginal-nej struktury.

Listing 12 przedstawia kod funkcji sendsms() wysyłającej SMS o treści podanej w parametrze aMessage pod numer telefo-nu z parametru aNumber. Na początku za po-mocą duetu LoadLibrary i GetProcAddress funkcja uzyskuje adresy niezbędnych funk-cji do obsługi SMS z biblioteki dynamicz-nej sms.dll. Następnie za pomocą funkcji SmsOpen, lub dokładniej za pomocą wskaźni-ka do tej funkcji zapamiętanego w zmiennej pSmsOpen, otwierany jest serwis SMS. Pierw-szy parametr funkcji określa nazwę protoko-łu, zastosowaną tu wartość "Microsoft Text SMS Protocol" można znaleźć w dokumen-tacji MSDN np. pod adresem [9]. Wartość 2 (SMS_MODE_SEND) w drugim parametrze okre-śla, że chcemy użyć API do wysyłania wia-domości, a nie do ich odbioru (SMS_MODE_RECEIVE, wartość 1). Trzeci parametr funk-cji SmsOpen() to zwracany uchwyt do serwi-su, stosowany w późniejszych wywołaniach do SmsSendMessage() i SmsClose().

Ostatni parametr określałby wydarzenie obsługujące asynchronicznie nadchodzące wiadomości. Omawiana funkcja tylko wysyła wiadomości, nie potrzebuje uchwytu wyda-rzenia i stosuje dla ostatniego parametru war-tość NULL. Po otwarciu serwisu funkcja prze-chodzi do wysłania wiadomości za pomocą SmsSendMessage(), również za pośrednic-twem wskaźnika do tej funkcji w sms.dll za-pamiętanego w zmiennej pSmsSendMessage.

Drugi parametr to numer centrum ob-sługi SMS, opakowany w strukturę _sms_address. Zastosowana wartość NULL zaleca pobranie standardowego numeru centrum skonfigurowanego w telefonie. Kolejny, trze-ci już parametr, to numer, pod który chcemy wysłać wiadomość, rzecz jasna również opa-kowany w strukturę _sms_address.

Dalsze dwa parametry to treść wiadomo-ści jako tekst UNICODE oraz jego rozmiar w bajtach. Dalej następują dane ze struktu-ry _provider_specific, określające, że wy-syłamy wiadomość tekstową (class = 1) oraz że chcemy dostać raport o dostarcze-niu tej wiadomości (options = 2). Osta-tecznie serwis zamykany jest za pomocą wskaźnika do funkcji SmsClose() i wywo-łaniem FreeLibrary() zwalniana jest bi-blioteka dynamiczna sms.dll.

Listing 13 przedstawia uproszczoną wer-sję głównej funkcji programu wysyłającego SMS za pomocą sendsms(). Główną funk-cją programów graficznych MS Windows nie jest funkcja main(), tylko WinMain().

Programy dla Windows CE nie stano-wią w tym względzie wyjątku. Trzeci pa-rametr tej funkcji szCmdLine to łańcuch znaków parametrów linii poleceń zasto-

Listing 17. Zrzut ekranu do mapy bitowej typu HBITMAP

HBITMAP screenshot() {

HBITMAP hBitmap;

HDC hDesktopDC, hBitmapDC;

int nH, nW;

hBitmap = NULL;

hBitmapDC = NULL;

retval = SS_UNKNOWN;

hDesktopDC = GetWindowDC(HWND_DESKTOP);

nW = GetSystemMetrics(SM_CXSCREEN);

nH = GetSystemMetrics(SM_CYSCREEN);

hBitmap = CreateCompatibleBitmap(hDesktopDC, nW, nH);

hBitmapDC = CreateCompatibleDC(hDesktopDC);

SelectBitmap(hBitmapDC, hBitmap);

BitBlt(hBitmapDC, 0, 0, nW, nH, hDesktopDC, 0, 0, SRCCOPY);

DeleteDC(hBitmapDC);

ReleaseDC(HWND_DESKTOP, hDesktopDC);

return hBitmap;

}

Listing 18. Konwersja mapy bitowej na format DIB

static int _bytesPerLine(int nWidth, int nBitsPerPixel) {

int retval;

retval = nWidth * nBitsPerPixel;

retval = ((retval + 31) & (~31)) / 8;

return retval;

}

void dibitmap(BITMAPINFO *theBI, LPVOID *theBuf, HBITMAP hBitmap) {

BITMAP bm;

HDC hdc, hmemdc, hcopydc;

HBITMAP htmpbmp;

BITMAPINFOHEADER *bih;

hdc = NULL;

hmemdc = NULL;

hcopydc = NULL;

ZeroMemory(theBI, sizeof(BITMAPINFO));

GetObject(hBitmap, sizeof(bm), &bm);

if (bm.bmHeight < 0) bm.bmHeight = -bm.bmHeight;

bih = &(theBI->bmiHeader);

bih->biSize = sizeof(BITMAPINFOHEADER);

bih->biWidth = bm.bmWidth;

bih->biHeight = bm.bmHeight;

bih->biPlanes = 1;

bih->biBitCount = bm.bmBitsPixel;

bih->biCompression = BI_RGB;

bih->biSizeImage = _bytesPerLine(bm.bmWidth, bm.bmBitsPixel) * bm.bmHeight;

hdc = GetDC(NULL);

htmpbmp = CreateDIBSection(hdc, theBI, DIB_RGB_COLORS, theBuf, NULL, 0);

hmemdc = CreateCompatibleDC(hdc);

hcopydc = CreateCompatibleDC(hdc);

SelectObject(hmemdc, hBitmap);

SelectObject(hcopydc, htmpbmp);

BitBlt(hcopydc, 0, 0, bm.bmWidth, bm.bmHeight, hmemdc, 0, 0, SRCCOPY);

DeleteDC(hmemdc);

DeleteDC(hcopydc);

}

Page 37: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia36

Programowanie Windows MobileRAPI

www.sdjournal.org 37

sowanych przy wywołaniu. W omawianej implementacji założono, że w linii pole-ceń będzie znajdował się numer telefonu oraz treść wiadomości, oddzielone od sie-bie znakiem spacji lub tabulatora. Dość długa pętla for na początku funkcji kopiu-je numer telefonu z początku linii pole-ceń do tablicy number oraz ustawia wskaź-nik cp na początek treści wiadomości w li-nii poleceń szCmdLine. Ostatecznie wywo-ływana jest funkcja sendsms(), załatwiają-ca bolesne szczegóły związane z wysłaniem wiadomości.

W kodzie źródłowym programów ([0]) omawiany program znajduje się w modu-łach sendsms.c oraz smsapp.c. Program można obecnie skompilować za pomocą CeGCC [7] oraz niestety niewspieranej już wersji Micro-soft Embedded Visual C 4.

Obecnie dostępne są binarne wersje CeGCC dla Linuksa oraz dla MS Windows wyposażonego we w miarę aktualny pakiet cygwin.

Można oczywiście pokusić się o samo-dzielną kompilację pakietu CeGCC ze źró-deł na teoretycznie dowolny system, choć mnie osobiście nie udało się z sukcesem za-kończyć kompilacji na moim OpenSuSE 11.1 x86_64. Na szczęście binarne 32-bi-towe pakiety rpm dla Mandrivy mandri-va-cegcc-cegcc-0.55-1.i586.rpm i mandri-va-cegcc-mingw32ce-0.55-1.i586.rpm, do-stępne na stronie projektu, pozwoliły się bezbłędnie zainstalować i uruchomić rów-nież pod 64-o bitowym OpenSuSE.

Kod źródłowy omawianego programu wystarczy potem skompilować za pomo-cą wywołania podobnego do tego z Listin-gu 14, proszę porównać plik makefile.cegcc dołączony do źródeł programu. Aby pro-gram skompilowany za pomocą CeGCC za-działał na urządzeniu przenośnym, należy dołączyć do niego bibliotekę dynamiczną cegcc.dll, zainstalowaną w katalogu arm-wince-cegcc/lib pakietu. Szczęśliwcy, któ-rym udało się we właściwym czasie załado-wać bezpłatną wersję kompilatora Micro-soft Embedded Visual C++ 4, mogą w za-mian skompilować program, używając wy-wołania podobnego do tego z Listingu 15, proszę również porównać plik makefile.evc dołączony do źródeł. Kompilacji można do-konać z okna konsoli pakietu kompilatora lub ustawiając zmienne środowiskowe tak, jak to pokazano na Listingu 16. Nie próbo-wałem kompilacji za pomocą aktualnych wersji Visual Studio, choć nie powinna się ona zbytnio różnić od tej przeprowadzonej za pomocą Embedded Visual C++ 4. Zakła-dając, że program z Listingu 10 skompilo-wany został jako rapirun, a opisywany tu program do wysyłania wiadomości teksto-wych nazwano sms.exe i skopiowano do ka-talogu \Temp na urządzeniu przenośnym,

możemy wysłać SMS, wywołując na kom-puterze biurkowym:

rapirun /Temp/sms.exe "+48123456789

Treść wiadomości tekstowej"

przy czym program powinien sobie bez pro-blemu poradzić również z polskimi znakami w treści wiadomości.

Zrzut ekranu PDAGenerowanie zrzutu ekranu urządzenia przenośnego to chyba najbardziej wdzięczne zadanie, z którym możemy sobie poradzić za pomocą zdalnego wywołania RAPI. W tym celu przygotowujemy program dla Windows CE nieposiadający interfejsu użytkownika, mogącego niepotrzebnie przesłonić interesu-jący nas obszar ekranu. Program jako para-

Listing 19. Konwersja mapy bitowej na format DIB

static void _write(HANDLE hFile, const LPVOID pBuf, DWORD nBytes) {

const BYTE* ptr;

DWORD remaining, len;

ptr = (const BYTE *) pBuf; remaining = nBytes;

while(WriteFile(hFile, ptr, remaining, &len, NULL) != 0 && remaining > 0) {

remaining -= len; ptr += len;

}

}

void savebmp(HBITMAP hBitmap, const TCHAR *szFileName) {

HANDLE hf;

BITMAPFILEHEADER bmpFileHeader;

BITMAPINFO bmpInfo;

LPVOID pBuf;

WORD w;

ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));

ZeroMemory(&bmpFileHeader, sizeof(BITMAPFILEHEADER));

pBuf = NULL;

hf = INVALID_HANDLE_VALUE;

dibitmap(&bmpInfo, &pBuf, hBitmap);

bmpFileHeader.bfReserved1 = 0;

bmpFileHeader.bfReserved2 = 0;

bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +

bmpInfo.bmiHeader.biSizeImage;

w = 'M'; w <<= 8; w += 'B'; /* w = 'MB'; */

bmpFileHeader.bfType = w;

bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

hf = CreateFile(szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,

CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

_write(hf, &bmpFileHeader, sizeof(BITMAPFILEHEADER));

_write(hf, &(bmpInfo.bmiHeader), sizeof(BITMAPINFOHEADER));

_write(hf, pBuf, bmpInfo.bmiHeader.biSizeImage);

CloseHandle(hf);

}

Listing 20. Główna funkcja programu do zrzutu ekranu

int WINAPI WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPWSTR szCmdLine,

int iCmdShow) {

HBITMAP hBitmap;

hBitmap = NULL;

hBitmap = screenshot();

savebmp(hBitmap, szCmdLine);

if (hBitmap != NULL) DeleteObject(hBitmap);

return 0;

}

Page 38: SDJ Extra 34 Biblia

SDJ Extra 34 Biblia38

Programowanie Windows Mobile

metr linii poleceń otrzymuje nazwę pliku, do którego zrzut zapisany zostanie jako mapa bi-towa. Brak graficznego interfejsu użytkowni-ka, przeszkoda nie do ominięcia na większo-ści urządzeń przenośnych nie wyposażonych w program linii poleceń cmd.exe, nie będzie stanowił żadnego problemu dla zdalnego wy-wołania z komputera stacjonarnego.

Listing 17 przedstawia uproszczoną wer-sję funkcji do generowania zrzutu ekranu do obiektu mapy bitowej typu HBITMAP. Za po-mocą GetWindowDC() uzyskiwany jest w hDesk-topDC kontekst dla podanej jako parametr sta-łej HWND_DESKTOP, czyli dla całego ekranu. Funk-cja CreateCompatibleBitmap() tworzy w hBit-map mapę bitową o rozmiarze i parametrach uzyskanego kontekstu. CreateCompatibleDC() tworzy jeszcze jeden kontekst, tym razem służą-cy do tworzenia obrazu w mapie bitowej. Obraz w mapie bitowej tworzony jest poprzez skopio-wanie obrazu z hDesktopDC za pomocą funk-cji BitBlt(). W ten sposób otrzymujemy w hBitmap mapę bitową zawierającą obraz okna HWND_DESKTOP, czyli obraz całego ekranu. Od mapy bitowej HBITMAP do mapy bitowej DIB spotykanej w plikach z rozszerzeniem bmp jest jednak jeszcze daleka droga. Przedstawiona na Listingu 18 funkcja dibitmap() przekształca mapę bitową hBitmap na format DIB zwraca-ny w buforze theBuf.

Dodatkowo zwraca w strukturze wska-zywanej przez theBI informacje o mapie bitowej, konieczne do późniejszego stwo-rzenia pliku. Sercem konwersji jest funk-cja CreateDIBSection() tworząca pustą mapę bitową w formacie DIB. Za pomo-cą znanego już tricku utworzenia poprzez CreateCompatibleDC() kontekstu dla ma-py bitowej oraz skopiowania obrazu z jedne-go kontekstu do drugiego za pomocą funkcji BitBlt() otrzymujemy w htmpbmp przemie-nioną mapę bitową. Jednym z parametrów wywołania CreateDIBSection był wskaźnik do bufora mapy bitowej (konkretnie wskaź-nik na wskaźnik, ponieważ wskaźnik na two-rzony bufor jest wartością zwracaną), zawie-rającego obraz już prawie w takiej postaci, ja-kiej oczekuje się od plików bmp.

Ostatnim krokiem jest zapisanie mapy bi-towej DIB do pliku. Listing 19 przedstawia funkcję savebmp() zapisującą mapę bitową w hBitmap podawaną w takim formacie, jak ją wyprodukowano w funkcji screenshot() do pliku o nazwie podanej w szFileName. Funkcja savebmp() najpierw wywołuje opi-saną funkcję dibitmap() w celu przekonwer-towania mapy bitowej na format DIB, zacho-wując wskaźnik na bufor otrzymanej mapy bitowej w zmiennej pBuf. Następnie tworzo-ny jest nagłówek pliku bmp, który zapisywa-ny jest do pliku tuż przed zapisaniem bajtów z bufora wskazywanego przez pBuf. Funkcja WriteFile() nie gwarantuje zapisania poda-nej ilości bajtów, dlatego zdefiniowano funk-

cję _write() wywołującą WriteFile() aż do zapisania całej zawartości bufora do pliku.

Listing 20 przedstawia uproszczoną wersję głównej funkcji WinMain() programu do two-rzenia zrzutu ekranu. Funkcja screenshot() umieszcza obraz ekranu w obiekcie mapy bi-towej wskazywanym przez uchwyt hBitmap. Za pomocą savebmp() obiekt konwertowany jest do formatu DIB i zapisywany do pliku o nazwie podanej w linii poleceń.

Program skompilować możemy w sposób po-dobny do przedstawionego przy opisanej wcze-śniej aplikacji do wysyłania wiadomości teksto-wych. Ponieważ w programie wykorzystano je-dynie API Win32, nie powinno być problemu z doborem kompilatora. Mnie powiodła się kom-pilacja zarówno za pomocą CeGCC, jak i Mi-crosoft Embedded Visual C++ 4.

Cały czas podkreślam, że listingi przedsta-wiają jedynie uproszczone wersje kodu, w pełni dostępnego pod adresem [0] w pliku źródłowym ce/scrsht.c. Listingi i tak są długie, a uwzględnienie w nich obsługi błędów oraz drobnych różnić między programami pisany-mi dla Windows CE oraz dla biurkowych wer-sji MS Windows (Przedstawiony tu sposób ge-nerowania zrzutów ekranu może być zastoso-wany również na biurkowych wersjach syste-mu MS Windows) dodatkowo zwiększyłoby ich rozmiar i zmniejszyło czytelność. Zacie-kawionych zapraszam do przestudiowania źródeł opisywanych programów.

Zakładając, że w pliku /Temp/scrsht.exe na urządzeniu przenośnym mamy już odpo-wiednio skompilowaną wersję programu oraz że wciąż mamy omawiany wcześniej program rapirun oraz program rapiget wykorzystujący funkcję wget() z Listingu 6, to zrzut ekranu możemy wykonać np. za pomocą następują-cych poleceń:

rapirun /Temp/scrsht.exe /Temp/plik.bmp

rapiget /Temp/plik.bmp plik.bmp

Zdalnie bez RAPIPod adresem [10] znajduje się projekt GNU-WINCE, zawierający skompilowane dla Win-dows CE podstawowe narzędzia znane z sys-temu UNIX, jak ls, cp itd. Dzięki demonowi rlogind oraz interpreterowi powłoki ush moż-liwe jest zalogowanie się na urządzeniu za po-mocą rlogin oraz zdalne uruchamianie pro-gramów. Projekt dostarcza nawet kompilator gcc, dzięki któremu można tworzyć progra-my konsolowe, uruchamiane z takiej właśnie sesji rlogin. Za pomocą demona ftpd oraz zwy-kłego programu ftp można transferować pliki w obydwie strony. Aby zainstalować GNU-WINCE na urządzeniu przenośnym, należy w najprostszym wypadku ściągnąć ze strony [10] plik winceos-092603.tar.bz2, rozpakować go i zawarte tam pliki wykonywalne skopio-wać do katalogu /bin urządzenia przenośne-go. Następnie na urządzeniu proszę urucho-

mić /bin/rlogind oraz /bin/ftpd, np. klikając na te programy w oknie eksploratora plików PDA. Pracę tych demonów można potem zakończyć np. w sesji rlogin, wywołując polecenie kill, przy czym identyfikator procesu można zna-leźć za pomocą – jakżeby inaczej – polecenia ps. Przykład sesji rlogin z Linuksa na moim wyposażonym w Windows Mobile 6 telefonie HTC3300 przedstawiłem na Rysunku 5.

Na koniecMam nadzieję, że udało mi się przybliżyć Wam procesy zachodzące w tle programów typu Mi-crosoft Active Sync i zachęcić do eksperymen-towania z własnymi programami. Przedstawio-ne w artykule proste aplikacje doskonale nadają się do zastosowania w skrypcie shell-owym lub pliku wsadowym MS Windows do automatyza-cji kopiowania plików z i na PDA, instalacji pa-kietów programowych czy po prostu do poszpe-rania po urządzeniu, którego standardowe na-rzędzia ani nie pokazują wszystkich plików ani nie umożliwiają dostępu np. do rejestru. Doku-mentacja RAPI pod adresem [1] opisuje wiele innych przydatnych funkcji, jak choćby dostęp do baz danych czy okien urządzenia oraz po-zwalających na uruchomienie specjalnie spre-parowanej biblioteki dynamicznej, umożliwia-jącej np. wysyłanie strumieni danych między komputerem biurkowym i urządzeniem. Dzię-ki projektowi SynCE programy komunikujące się z Windows CE i Mobile przestały być do-meną biurkowych wersji systemu firmy Micro-soft i można je z powodzeniem implementować również pod Linuksem i FreeBSD. Ani ilość sys-temów operacyjnych wspieranych przez Syn-CE ani ilość zaimplementowanych tam funk-cji RAPI nie jest jeszcze imponująca, tego typu projekty rozwijają się jednak dosyć szybko i kto wie, być może ktoś z Was zachęcony artykułem jeszcze trochę je przyspieszy (i poprawi np. bar-dzo nieekonomicznie zaimplementowany spo-sób konwersji z i na UCS-2LE, który – jak sobie przynajmniej wmawiam - zaimplementowałem nieco lepiej).

Zachęcam również wszystkich do odwie-dzenia strony opisywanego tu projektu [0], za-wierającej oprócz wersji źródłowych również skompilowane wersje binarne dla OpenSuSE 11.1, MS Windows oraz dla Windows CE. Za-warte w źródłach pliki makefile wykorzystu-ją zarówno kompilatory firmy Microsoft jak i bezpłatny kompilator GCC, dla wszystkich wspieranych systemów operacyjnych. I, last but not least, namawiam do rozwijania (i rzecz jasna poprawiania błędów) projektu, nie po-przestawajcie tylko na cichym wyśmiewaniu się z niedociągnięć.

DANIEL STOIŃSKIInformatyk w firmie Nagler & Company (http://www.nagler-company.com)Kontakt z autorem: [email protected]

Page 39: SDJ Extra 34 Biblia

Rekru

tacy

jny.p

l Dla naszego klienta, światowego lidera w branży telekomunikacyjnej (przy projekcie obejmującym rozwój aplikacji nawigacyjnej oraz lokalizacyjnej na urządzenia mobilne) poszukujemy:

Symbian DeveloperMiejsce 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łaMiejsce: BerlinTryb 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 angielskiegoMile widziane:• Doświadczenie w tworzeniu aplikacji pod J2ME lub Windows MobileNasz klient zapewnia:• Praca przy bardzo ciekawym projekcieOraz: 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: [email protected] 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 aplikacji nawigacyjnej oraz lokalizacyjnej na urządzenia mobilne) poszukujemy:

Senior J2ME DeveloperMiejsce pracy: BerlinNr ref.: J2ME/Berlin

Warunki projektu:Wynagrodzenie: 18.000 PLN / m-c Czas trwania: 9 - 12 m-cy - z opcją przedłużenia (po zakończeniu kontraktu firma oferuje udział w innych równie ciekawych projektach)Rozpoczęcie: Rekrutacja ciągłaMiejsce: BerlinTryb 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 angielskiegoMile 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 projekcieOraz: 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: [email protected] 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)”

Rekru

tacy

jny.p

l

Page 40: SDJ Extra 34 Biblia

40

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Rozpocznij przygodę z Windows Mobile

www.sdjournal.org 41

W dobie XXI wieku urządze-nia mobilne odgrywają bardzo ważną rolę. Dostęp do takich

urządzeń jest praktycznie nieograniczony, a gamma systemów operacyjnych pozwa-la na zróżnicowanie rynku. Firma Micro-soft jest jedną z firm, która oferuje produ-centom urządzeń mobilnych system swo-jej produkcji. Urządzenia z systemem Win-dows Mobile zyskują coraz większą po-pularność poza strefą biznesową. Jedną z przyczyn takiej sytuacji jest ciągle obniża-jąca się cena, a także zmieniające się zapo-trzebowanie użytkowników.

Mimo to, urządzenia takie jak PocketPC świetnie spisują się w innych sytuacjach, niż biznesowe czy rozrywka. Dużą rolę od-grywają w przemyśle czy medycynie, gdzie wspierane przez dodatkowe oprogramowa-nie wspierają, a jednocześnie przyspiesza-ją pracę.

Urządzenia takie mogą być rozszerzane o inne elementy, np. drukarki mobilne czy czytniki kodów kreskowych. To wszystko sprawia, iż programowanie aplikacji mo-bilnych przynosi ogromną satysfakcję pro-gramistom.

W artykule tym zostanie pokazane, jak można łatwo i szybko zacząć pracę z urzą-dzeniami mobilnymi, a co najważniejsze, na samym początku nie potrzebujemy fi-zycznego urządzenia, gdyż Microsoft Vi-sual Studio dostarcza nam odpowiednie emulatory.

Ważnym elementem są wymagania. Aby zacząć pracę z urządzeniami mobilnymi, będziemy potrzebowali Microsoft Visu-al Studio 2008 w wersji Standard lub wyż-szej. Darmowe narzędzie Microsoft Visu-al Studio 2008 Express nie wspiera apli-kacji mobilnych, przy czym, by przetesto-wać, jak wygląda programowanie aplika-cji mobilnych, możemy pobrać za darmo wersję 90-dniową ze stron firmy Micro-soft: http://msdn.microsoft.com/en-us/vstudio/default.aspx.

Nasz Pierwszy ProgramMógłbym tu opowiadać o historii linii pro-duktów dla programistów firmy Microsoft, jak również o tym, jak zmieniały się dostęp-ne elementy urządzeń mobilnych z wersji na wersję, ale w ten sposób zwiększy się przewaga nudnej historii, która i tak niko-go nie nauczy programować, dlatego od ra-zu rozpoczniemy przygodę z programowa-niem. Po uruchomieniu Microsoft Visual Studio 2008 klikamy w File\New\Project. Pojawi się nowe okno New Project podobne do tego przedstawionego na Rysunku 1.

Jest to bardzo intuicyjne okno, z lewej strony wybieramy typ projektu, w naszym przypadku jest to Smart Device. Po prawej stronie pozostawimy wszystko bez zmian, gdyż dalszej konfiguracji dokonamy w ko-lejnym oknie. W tym oknie ważne jest po-danie nazwy projektu oraz jego lokaliza-cji. Informacje te możemy wpisać u dołu okna. Jeśli uzupełniliśmy wszystkie infor-macje, możemy kliknąć przycisk OK., by przejść dalej.

W kolejnym oknie (Rysunek 2) doko-nujemy dokładnej specyfikacji naszej apli-kacji. W pierwszej kolejności wybieramy platformę, na której będzie uruchamiana nasza aplikacja. Po domyślnej instalacji Mi-crosoft Visual Studio 2008 mamy dostęp do Pocket PC 2003, Windows CE, Win-dows Mobile 5.0 Pocket PC SDK oraz Win-dows Mobile 5.0 Smartphone SDK. Py-tanie, co jeśli chcemy stworzyć aplikację przystosowaną dla Windows Mobile 6.0? Musimy w takim przypadku doinstalować odpowiednie SDK, link do nich znajduje się u dołu okna Download additional emu-lator images and smart devices SDKs…. Od-nośnik przekieruje nas na strony firmy Mi-crosoft, z której możemy pobrać dodatko-we SDK, jak również przeczytać więcej nt. systemu Windows Mobile. Na tym etapie nie musimy pobierać dodatkowego SDK i możemy wybrać Windows Mobile 5.0 Po-cket PC SDK (ekran dotykowy) lub Win-dows Mobile 5.0 Smartphone SDK (ekran niedotykowy). Osobiście proponuję wybór tego pierwszego, gdyż możemy tutaj prze-testować, jak pisać aplikacje z dotykowym ekranem.

Jako Target platform wybraliśmy Windows Mobile 5.0, ale nie przeszkadza nam nic, by uruchomić taką aplikację na urządzeniu z systemem Windows Mobile 6.0. Firma Mi-

Rozpocznij Przygodę z Windows MobileFirma 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.

Dowiesz się:• Jak stworzyć oraz uruchomić swoją pierw-

szą aplikację na urządzeniu z systemem Windows Mobile;

• Jakie możliwości daje nam .NET Compact Framework.

Powinieneś wiedzieć:• Jak obsługiwać narzędzie Microsoft Visual

Studio.

Poziom trudności

Zobacz, jak to się robi w technologii Microsoft

Page 41: SDJ Extra 34 Biblia

40

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Rozpocznij przygodę z Windows Mobile

www.sdjournal.org 41

crosoft dołożyła wszelkich starań, by każdy system nowszy z rodziny Mobile był kom-patybilny z wersją niższą. Jeśli jednak użyje-my elementów pochodzących z wersji 6.0, to nie zawsze będzie możliwe uruchomienie ta-kiej aplikacji na urządzeniu z wersją 5.0 Win-dows Mobile.

Kolejnym etapem jest wybór odpowiednie-go Framework`a. Zalecane jest, by wybierać jak najnowszy, gdyż mamy dostęp do wielu nowych elementów, jak choćby Microsoft LI-NQ, który jest dostępny od wersji 3.5 Micro-soft .NET Compact Framework.

Ostatnim etapem jest wybór szablonu (Template). Do wyboru mamy:

• Device Application – dostajemy przygo-towany projekt, który będzie korzystał z formatek oraz będzie posiadał reprezen-tację graficzną.

• Class Library – projekt, w którym zo-staną przygotowane klasy na późniejszy użytek innych projektów.

• Console Application – mamy możliwość tworzenia aplikacji konsolowej. Z ra-cji tego, iż system Windows Mobile nie posiada konsoli, nie jesteśmy w stanie przedstawić w sposób wizualny efektów naszej pracy.

• Control Library – projekt, w którym zo-staną przygotowane kontrolki, które bę-dziemy mogli umieszczać na naszych formatkach w głównym projekcie.

• Empty Project – całkowicie pusty projekt.

Wybieramy Device Application, gdyż nie chcemy zaczynać od zera, lecz wykorzystać już pewne elementy, a Visual Studio przy-gotuje całe środowisko za nas. Naszym za-daniem będzie dodanie pozostałych ele-mentów.

Ostatecznie klikamy OK., po czym nasz projekt zostanie przygotowany. Na początku załaduje się czysta formatka, która będzie oto-czona skórką przykładowego urządzenia, ma-jąca w pewien sposób ułatwić nam sposób wy-obrażenia sobie, jaki będzie efekt końcowy.

Pierwszym elementem, który warto zmie-nić, to nazwa formatki. Przechodzimy do Solution Explorer, klikając w View\Solution Explorer, i na liście klikamy prawym klawi-szem myszy na Form1.cs. Nazwę proponuję zmienić na MainForm.cs, ale może ona być dowolna.

Kolejnym elementem będzie zmiana usta-wień naszej formatki, w tym celu klikamy gdzieś w środku formatki (wewnątrz skór-ki urządzenia), a następnie z menu wybiera-my Properties. Okno ustawień formatki po-winno być podobne do tego przedstawione-go na Rysunku 3.

W tym miejscu możemy dokonać wszel-kich modyfikacji naszej formatki. Nie będzie-my tutaj wymieniać wszystkich opcji, a sku-

pimy się na tych, które przydadzą się w na-szym projekcie.

• AutoScroll – jeśli jest ustawione na True(wartość domyślna), to mamy pew-ność, że w przypadku, gdy nasze kontro-lki nie mieszczą się na formie, zostanie dodany suwak.

• BackColor – ustawia kolor formatki.• Font – czcionka i styl stosowane na format-

ce (element Font nie jest dziedziczony dla kontrolek umieszczanych na formatce).

• ForeColor – kolor czcionki.• FormFactor – pozwala na zmianę sposo-

bu wyświetlania formatki względem te-go, na jakie urządzenie jest przeznaczo-na nasza aplikacja.

• MinimizeBox – (domyślna wartość True) – pozwala ustawić, czy okno ma być mi-nimalizowane, czy zamykane (False). W przypadku systemu Windows XP, Vi-sta czy 7 każde okno u góry po prawej ma czerwony X, który jest równoznacz-ny z poleceniem zamknij aplikację. W

Rysunek 1. Okno dodawania nowego projektu

Rysunek 2. Dodanie większej ilości informacji nt. projektu

Page 42: SDJ Extra 34 Biblia

42

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Rozpocznij przygodę z Windows Mobile

www.sdjournal.org 43

przypadku Windows Mobile nie jest do-konywane polecenie zamknij, a zmini-malizuj. By była możliwość zamykania formatki, musimy zmienić tę opcję na False. Jeśli teraz popatrzymy na okno formatki, to zauważymy, że nie ma przy-cisku X, lecz ok., które pozwala zamknąć okno formatki.

• Text – pozwala na ustawienie tekstu wyświetlanego na pasku u góry for-matki.

• WindowState – domyślnie jest ustawio-ne Normal, co jest równoznaczne z tym, co widzimy na przykładowej formatce. Możemy jednak zmienić na Maximized, co spowoduje usunięcie górnej części okna formatki. Uzyskujemy więcej miej-sca, lecz funkcję zamknij czy zminimali-zuj musimy teraz osadzić w menu.

Jest jeszcze jedno bardzo ważne ustawie-nie formatki. Klikając prawym klawiszem myszy w środku skórki, z menu wybieramy Rotate Left lub Rotate Right. W efekcie skór-ka zostanie przystosowana do innego usta-wienia urządzenia mobilnego. W przypad-ku urządzeń mobilnych mamy bardzo du-że zróżnicowanie nie tylko w wielkości i rozdzielczości ekranu, ale także w jego po-łożeniu.

Może to być domyślnie pionowy, jak rów-nież poziomy. Do zadań programisty należy zaprojektowanie formatki, by dostosowywała się automatycznie do rodzaju urządzenia. W dalszej części artykułu zostaną dodane różne elementy na formatkę i zostanie dokładnie omówione, co należy zrobić, by zaprojekto-wać bardzo dobry interfejs.

Możemy uznać, iż nasza aplikacja jest go-towa do uruchomienia. Aplikacja domyślnie uruchamia się w symulatorze, co w naszym przypadku jest jak najbardziej odpowied-nie. Uruchomić aplikację możemy na kilka sposobów: klikając w zieloną strzałkę na pa-skach Visual Studio lub klikając klawisz [F5] (są także inne sposoby, ale nie będziemy ich teraz omawiać).

Po kliknięciu klawisza [F5] lub przyciśnię-ciu zielonej strzałki zostanie skompilowany nasz projekt, uruchomiony emulator, a osta-

tecznie załadowany nasz projekt na urządze-nie mobilne. Nasza przykładowa aplikacja może wyglądać jak ta przedstawiona na Ry-sunku 4.

W taki oto sposób stworzyliśmy format-kę pod pierwszy program. Teraz dodamy ele-menty menu oraz kilka kontrolek, które na-stępnie oprogramujemy.

Konfiguracja MenuW celu dodania elementu do menu klikamy w jasnoniebieski pasek u dołu skórki. Efek-tem będzie pojawienie się po lewej stronie podświetlonego tekstu Type Here, co jest za-chętą, by kliknąć w to miejsce. Klikamy w na-pis i dodajemy własny: Zamknij. W tym miej-scu będzie możliwość zamknięcia naszego programu. Warto wspomnieć, iż w progra-mach na urządzenia mobilne od wersji szó-stej Windows Mobile został wprowadzony

Rysunek 3. Właściwości głównej formatki

Listing 1. Podstawowa część kodu

using System.Windows.Forms;

namespace SDJStartWithWindowsMobile

{

public partial class MainForm : Form

{

public MainForm()

{

InitializeComponent();

}

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

{

Close();

}

}

}

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

using System.Windows.Forms;

namespace SDJStartWithWindowsMobile

{

public partial class MainForm : Form

{

public MainForm()

{

InitializeComponent();

}

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

{

Close();

}

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

{

MessageBox.Show("Witaj " + textBox1.Text, "Powitanie");

}

}

}

Page 43: SDJ Extra 34 Biblia

42

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Rozpocznij przygodę z Windows Mobile

www.sdjournal.org 43

umowny standard, aby najczęściej używana funkcja znajdowała się po lewej stronie me-nu, a reszta ukryta jako podmenu po prawej stronie, pod klawiszem MENU. Rysunek 5 prezentuje przykładowe menu.

Jeśli zobaczymy na Rysunku 5, że poza skórkę z prawej strony wystaje jeszcze jeden przycisk zachęty, do kontynuowania rozsze-rzania menu. Nie jest zalecane i nie należy do najlepszych praktyk, by menu było więk-sze, niż dwa przyciski umieszczone w pasku menu, zagnieżdżone podmenu również nie są zalecane. Rysunek 6 prezentuje źle zapro-jektowane menu.

Skoro już mamy nasze menu gotowe, to do-dajmy teraz kod pod przycisk Zamknij, by po naciśnięciu tego klawisza menu nasza aplika-cja się zamykała. Klikamy dwukrotnie w przy-cisk Zamknij, Visual Studio automatycznie przeniesie nas do kodu, a do tego doda nie-zbędny blok kodu, który będzie wywoływa-ny za każdym razem, gdy zostanie naciśnię-ty przycisk Zamknij. Między nawiasem {„, a „} wpisujemy Close();, co jest równoznacz-ne z zamknięciem formatki. Listing 1 przed-stawia część kodową naszej formatki.

Możemy teraz przetestować naszą aplika-cję, uruchamiając i testując klawisz Zamknij.

Wróćmy teraz do naszej formatki (przej-ście między częścią kodową a wizualną może-my dokonać, klikając w klawisz [F7]) i dodaj-my kilka kontrolek.

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

czynając od Label, a prawe krawędzie rozsze-rzamy do prawej krawędzi formatki. Obróć-my formatkę w lewo lub prawo.

Szerokość kontrolki TextBox nie powięk-sza się wraz ze zmianą położenia formatki, co jest złą praktyką, dlatego też przejdźmy te-raz do właściwości Anchor kontrolki TextBox. Domyślnie mamy tutaj Top,Left, dodajemy jeszcze Right. Obracamy ponownie naszą formatkę i mamy bardzo ładnie zaprojekto-waną formatkę, która automatycznie dosto-sowuje się do urządzenia mobilnego.

Teraz chcielibyśmy, by po wpisaniu tekstu w kontrolkę TextBox pojawił się komunikat powitalny. Jeśli jest urządzenie z dotykowym ekranem, tak jak w naszym przypadku, to możemy pokusić się o umieszczenie przyci-sku na formatce, co nie do końca jest dobrym rozwiązaniem, gdyż użytkownik będzie zmu-szony do obsługi urządzenia dwiema rękami, co nie jest korzystne, dlatego wszelkie tego ty-pu operacje powinny być umieszczane w me-nu. Jeśli byłaby to aplikacja przeznaczona dla urządzeń bez ekranu dotykowego, to wręcz koniecznym jest umieszczenie operacji powi-tania w menu.

Do menu dodajemy przycisk Witaj, a na-stępnie klikamy w niego dwukrotnie, co spo-woduje przejście do kodu. Tutaj uzupełniamy kod o dodatkowe elementy. Przykład został za-prezentowany na Listingu 2, natomiast efekt działania programu prezentuje Rysunek 6.

W tym miejscu zakończymy wstęp do two-rzenia aplikacji mobilnych. Teraz przejdzie-my i przyjrzymy się elementom, które do-starcza nam SDK, a więc klasom, które dadzą nam dostęp do najciekawszych miejsc urzą-dzeń mobilnych, które mamy możliwość mo-nitorować, jak również, co ważniejsze, zmie-niać. Będzie trochę więcej elementów za-awansowanych, skupimy się także bardziej na stronie kodowej.

Elementy SDKWszystkie klasy pochodzące z przestrzeni nazw System są częścią .NET Compact Fra-mework, natomiast wszystkie należące do Mi-crosoft to elementy SDK. Przyjrzymy się teraz wybranym klasom w przestrzeni nazw Micro-soft.WindowsMobile. W tabelce zawarto prze-strzenie nazw, które zostaną dokładnie omó-wione, oraz ich znaczenie w projekcie.

W celu wykorzystania którejkolwiek z klas, należy dodać referencje do projektu. W tym celu otwieramy SolutionExplorer i klika-my prawym klawiszem myszy w References, a następnie Add Reference. Pojawi się kolejne okno, w którym mamy możliwość wyboru odpowiednich referencji. Wybieramy wszyst-kie wymienione, a dodatkowo dodajemy Mi-crosoft.WindowsMobile.

Poniżej zostaną omówione wybrane prze-strzenie nazw, jak również pokazane zostaną wybrane elementy tychże przestrzeni.

Rysunek 4. Pierwsza przykładowa aplikacja

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

Tabela 1. Przestrzenie nazw zawarte w WindowsMobile

WindowsMobile.Configuration Mamy dostęp do elementów konfiguracyjnych urządze-nia mobilnego.

WindowsMobile.Forms Dzięki tej przestrzeni nazw mamy dostęp do elementów takich jak wybór kontaktu czy zdjęcia.

WindowsMobile.PocketOutloook Dostęp do kontaktów oraz do elementów sterujących wysyłaniem SMS`ów.

WindowsMobile.Status Możemy kontrolować status urządzenia mobilnego.

WindowsMobile.Telephony Elementy związane z dokonywaniem rozmów telefo-nicznych.

Page 44: SDJ Extra 34 Biblia

44

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Rozpocznij przygodę z Windows Mobile

www.sdjournal.org 45

W części kodu dodajemy odpowiednie od-wołania do przestrzeni nazw. Listing 3 poka-zuje, jak to zrobić.

WindowsMobile.FormsPrzy pomocy tej przestrzeni nazw możemy wybierać konkretny kontakt z naszej listy kontaktów czy też wybrać zdjęcie. By najle-piej przedstawić działanie tejże przestrzeni nazw, do menu dodamy dwa podmenu: Zdję-cie oraz Kontakt, a dla nich utworzymy część kodową, klikając w każdy dwukrotnie. Li-sting 4 przedstawia kod, który należy zamie-ścić w każdej z metod.

Pierwsza część kodu umożliwia wybór zdjęcia, a następnie przypisanie jego nazwy do właściwości Text kontrolki Label, któ-ra została umieszczona na formatce w po-przedniej części artykułu.

Druga część kodu jest bardzo podobna do poprzedniej, z tym że teraz pozwalamy na wybór kontaktu z listy kontaktów, a następ-nie przypisujemy nazwę kontaktu do właści-wości Text kontrolki Label.

Jak można zauważyć, klasy te są do siebie bardzo podobne, co świadczy o doskonałym zaprojektowaniu tychże klas przez projektan-tów z firmy Microsoft.

WindowsMobile.StatusDziałanie tej przestrzeni nazw najlepiej zaprezentować na przykładzie, który zo-stał umieszczony na Listingu 5. Klasa SystemState jest bardzo bogata, a w przy-kładzie pobieramy nazwę właściciela urzą-dzenia mobilnego i przypisujemy ją do wła-ściwości Text kontrolki Label. Jeśli dokład-nie się przyjrzymy metodom, jakie ofe-ruje klasa SystemState, to szybko może-

Rysunek 7. Przykład działania programu

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

using Microsoft.WindowsMobile.Forms;

using Microsoft.WindowsMobile.Telephony;

using Microsoft.WindowsMobile.PocketOutlook;

using Microsoft.WindowsMobile.Status;

Listing 4. Możliwość wybrania zdjęcia oraz kontaktu

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

{

var selectPictureDialog = new SelectPictureDialog();

var dialogResult = selectPictureDialog.ShowDialog();

if(dialogResult == DialogResult.OK)

{

label1.Text = selectPictureDialog.FileName;

}

}

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

{

var contactDialog = new ChooseContactDialog();

var dialogResult = contactDialog.ShowDialog();

if(dialogResult == DialogResult.OK)

{

label1.Text = contactDialog.SelectedContactName;

}

}

Listing 5. Dostęp do statusów

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

{

label1.Text = SystemState.OwnerName;

}

Listing 6. Aplikacja działająca w tle.

private SystemState _st;

private static readonly string _id = "SDJ";

private void MainForm_Load(object sender, EventArgs e)

{

if (SystemState.IsApplicationLauncherEnabled(_id))

{

_st = new SystemState(_id);

_st.Changed+=new ChangeEventHandler(_st_Changed);

}

else

{

_st = new SystemState(SystemProperty.TasksActive);

_st.Changed += new ChangeEventHandler(_st_Changed);

_st.EnableApplicationLauncher(_id);

}

}

void _st_Changed(object sender, ChangeEventArgs args)

{

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

}

private void MainForm_Closed(object sender, EventArgs e)

{

_st.Dispose();

}

Page 45: SDJ Extra 34 Biblia

44

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Rozpocznij przygodę z Windows Mobile

www.sdjournal.org 45

my stwierdzić, iż jest to jedna z bardziej przydatnych klas, gdyż możemy dokład-nie sprawdzić każdy stan w systemie Win-dows Mobile i zareagować w odpowiedni sposób. Zobaczmy, jak przy pomocy Sys-temState możemy uruchomić naszą apli-kację w tle. Listing 6 pokazuje przykłado-we rozwiązanie tego problemu. Do naszej formatki dodajemy obsługę Event`u Load oraz Closed, po czym w kodzie tworzymy prywatne obiekty _st, oraz _id. W częście MainForm_Closed dodajemy obsługę za-mknięcia obiektu SystemState, natomiast w części MainForm_Load dodamy waru-nek, który będzie sprawdzał, czy została uruchomiona aplikacja o podanym iden-tyfikatorze. Jeżeli nie, to przechodzimy do części else, w której tworzymy nowy obiekt SystemState, który będzie reagował na do-danie nowego zadania, o czym poinformu-je w nowym oknie, oraz wskaże, iż aplikacja będzie miała dany identyfikator, jeśli jed-nak warunek zostanie spełniony, to zosta-nie stworzony nowy obiekt SystemState z podanym identyfikatorem oraz zostanie wyświetlona informacja. Po uruchomie-niu programu, oraz dodaniu nowego za-dania urządzenie mobilne wyświetli nasz program wraz z komunikatem. Jeśli teraz zamkniemy naszą aplikację, co spowoduje

działanie aplikacji w tle, i dodamy nowe za-danie, to nasza aplikacja zostanie ponownie włączona wraz z komunikatem, pomimo iż szybciej dokonaliśmy jej zamknięcia.

WindowsMobile.PocketOutlookW PocketOutlook mamy dostęp do takich elementów jak kontakty, zadania, oraz spo-tkania. Możemy je dodawać, jak również czy-ścić, a co najważniejsze reagować w odpo-wiedni sposób w przypadku zmiany któregoś z elementów. Listing 6 przedstawia przykład, jak można w prosty sposób dodać nowy kon-takt do listy kontaktów.

Na początku tworzymy nowy obiekt OutlookSession, następnie tworzymy nowy kontakt oraz dodajemy go do listy kontak-tów zawartych w OutlookSession. Dalej do-dajemy elementy kontaktu oraz aktualizuje-my kontakt o nowe informacje. Ostatecznie zamykamy OutlookSession.

WindowsMobile.TelephonyJest to dość konkretna przestrzeń nazw, a jej przeznaczenie jest dość oczywiste. Za jej po-mocą możemy dokonywać rozmów telefo-nicznych. Do naszego menu dodajemy no-we podmenu Zadzwoń i klikamy dwukrot-nie, oraz dodajemy część kodu zawartą na Li-stingu 8.

Kod wydaje się trywialnie prosty, gdyż tworzymy nowy obiekt Phone, który jest bez-parametrowy, a następnie wywołujemy jego metodę Talk, która może przyjmować jeden lub dwa parametry. Pierwszym jest numer te-lefonu, natomiast drugi określa, czy ma zo-stać wyświetlone zapytanie przed wykona-niem rozmowy.

Podczas uruchamiania tej części kodu pro-szę zwrócić uwagę, by aplikacja była urucha-miana w emulatorze Windows Mobile 6 Pro-fessional.

PodsumowanieUrządzenia mobilne mają coraz to now-sze zastosowania i zaczynają wypierać stan-dardowe urządzenia, a ich wszechobecność umożliwia nam, jako programistom, pisa-nie aplikacji, które trafią do dużej ilości użytkowników.

W artykule tym zostały przedstawione in-formacje wstępne, które pozwolą zasmako-wać programowania aplikacji mobilny dla systemu Windows Mobile. Wiedzę należało-by poszerzyć o połączenie z mobilną bazą da-nych czy synchronizację bazy mobilnej z ba-zą stacjonarną.

Oprogramowanie Microsoft Visual Stu-dio 2008 dostarcza bardzo dużo narzędzi programistycznych, które można wykorzy-stać na różne sposoby, lecz podczas two-rzenia projektu ważny jest także czas pisa-nia aplikacji. Autor poleca dodatek do Vi-sual Studio firmy JetBrains o nazwie Re-Sharper, który przyspieszy pracę programi-sty, jak również pozwoli utrzymać czytelny kod projektu. Program można zamówić ze strony producenta: http://www.jetbrains.com/resharper.

Na końcu chciałbym wspomnieć o Win-dows Mobile Marketplace (http://developer.windowsmobile.com/Marketplace.aspx). Jest to swego rodzaju sklep internetowy, który ofe-ruje aplikacje firm trzecich, co oznacza, że możemy napisać aplikację i dodać ją do bazy oprogramowania, a dowiedzą się o niej klien-ci z całego świata. Polska jest na liście krajów, które jako pierwsze będą mogły udostępniać sprzedaż aplikacji za pomocą Windows Mo-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.

DANIEL DUDEKPracuje w firmie LGBS Software na stanowisku .NET Developer. Zajmuje się programowaniem aplikacji mobilnych na systemach Microsoft Win-dows Mobile. Jest liderem Śląskiej Regionalnej Grupy Microsoft. Autor jest także prelegentem konferencji społecznościowych: ht tp : //ms- groups . pl /sp eakerbiuro /Strony/DanielDudek.aspxKontakt z autorem: [email protected]

W Sieci

• Strona dla programistów aplikacji mobilnych dla urządzeń z systemem Windows Mobile http://developer.windowsmobile.com;

• Strona poświęcona programowaniu aplikacji mobilnych, zawarta jako część MSDN http://msdn.microsoft.com/pl-pl/windowsmobile/default(en-us).aspx;

• Blog autora artykułu http://geekswithblogs.net/dand;• Witryna poświęcona urządzeniom mobilnym https://www.microsoft.com/windowsmobile/

pl-pl/default.mspx;• Windows Mobile Marketplace – globalny sklep z aplikacjami Windows Mobile http://deve-

loper.windowsmobile.com/Marketplace.aspx.

Listing 7. Dodanie nowego kontaktu.

private void menuItemPocketOutlook_Click(object sender, EventArgs e)

{

var outlookSession = new OutlookSession();

var contact = outlookSession.Contacts.Items.AddNew();

contact.FirstName = "SDJ";

var uri = new Uri("http://www.sdjournal.org");

contact.WebPage = uri;

contact.Update();

outlookSession.Dispose();

}

Listing 8. Metoda pozwalająca wykonać rozmowę

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

{

var p = new Phone();

p.Talk("1234", true);

}

Page 46: SDJ Extra 34 Biblia

46

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 47

Za sprawą Visual Studio oraz platfor-my .NET pisanie aplikacji pod Win-dows Mobile coraz bardziej przypo-

mina pisanie oprogramowania na desktopy. Z każdą nową wersją .NET Compact Fra-mework rośnie liczba dostępnych klas. Pisa-nie aplikacji staje się coraz łatwiejsze, szcze-gólnie jeśli piszemy aplikacje w języku Visu-al C# czy Visual Basic.NET. Microsoft stara się również ujednolicać narzędzia programi-styczne takie jak Visual Studio. Dzięki te-mu, kiedy raz przyzwyczaimy się do niego oraz nauczymy się .NETa, możemy pisać w nich aplikacje Windows Forms, Web Forms, Windows Mobile czy pisać interaktywne aplikacje w Silverlight. Oczywiście każde z tych rozwiązań również różni się od siebie. Przykładowo, pisząc aplikację pod Windows Mobile, należy zdawać sobie sprawę, że urządzenie mobilne nie ma takiej mocy ob-liczeniowej co komputer desktopowy. Z ko-lei na pewno cechą wspólną wszystkich po-wstających aplikacji jest wykorzystywanie i przetwarzanie w nich danych. O ile na desk-topach powstało do tego celu wiele rozwią-zań, o tyle na Windows Mobile sprawa nie wygląda tak dobrze. Jednym z rozwiązań

jest opisany tutaj SQL Server CE. Ciekawą cechą tego rozwiązania jest wykorzystanie tej wersji serwera również na desktopach. Początkowo SQL Server CE był stworzony tylko dla urządzeń mobilnych. Od niedaw-na powstała specjalna dystrybucja tego opro-gramowania umożliwiająca wczytywanie bazy danych z pliku bez wykorzystania ja-kiejkolwiek usługi SQL Se-rvera również na kompute-rach PC. Fizycznie SQL Se-rver CE (zarówno mobi-le, jak i desktop) jest zbio-rem bibliotek, a cała baza da-nych składowana jest w po-staci jednego pliku z rozsze-rzeniem sdf. Wszystkie zapy-tania, połączenia i narzędzia SQL Server CE w wersji mo-bilnej i desktopowej są ze so-bą kompatybilne. Ta ostatnia wersja (desktop) szczególnie może się przydać, gdy rekor-dów w bazie nie jest na tyle dużo, aby zaistniała koniecz-ność instalacji pełnej wersji Microsoft SQL Server. Przy-kładowo programista tworzy aplikację – katalog produk-tów danej firmy. Załóżmy, że będzie to katalog drobnych części samochodowych. Ba-

za będzie zawierać 30000 części. Wszystko będzie wczytywane z 1 płyty CD. Wykorzy-stanie do tego SQL Servera jest przesadą. In-nym rozwiązaniem może być Linq to XML. Jednak tak jak pokazały testy, najszybszym rozwiązaniem będzie wykorzystanie wła-śnie SQL Server CE for desktop (patrz roz-dział testy). Właśnie po to powstał SQL Se-rvera CE (desktop i mobile). Jest to pomost pomiędzy olbrzymią ilością danych w du-żych firmach, gdzie wykorzystuje się pełną wersję Microsoft SQL Server, a bardzo małą ilością danych – parsowanie XMLa.

Oczywiście każde rozwiązanie ma swój obszar zastosowań. Wszystko zależy od oczekiwań programisty – i na tej podsta-wie powinniśmy wybrać odpowiednie na-

MS SQL SERVER CE 3.5

Dzięki nowej wersji SQL Servera CE możesz zarządzać danymi poprzez zapytania bez konieczności instalacji dodatkowych usług i bez dostępu do Internetu. Ułatwia to dystrybucję oprogramowania. W artykule znajdziesz informacje, które pozwolą Ci rozpocząć pisanie aplikacji wykorzystujących SQL Server CE.

Dowiesz się:• Jakie możliwości niesie ze sobą SQL Server

CE 3.5;• Jak go wykorzystywać w swoich aplikacjach;• Jak pracować z nim w Visual Studio;• Jakie są dostępne narzędzia wspierające;• Jakie są rodzaje replikacji;• Jaka jest wydajność tego rozwiązania.

Powinieneś wiedzieć:• Znajomość podstawowych zapytań języka

T-SQL;• Podstawowa wiedza z zakresu .NET CF 3.5;• Podstawowa wiedza z zakresu C# 3.5.

Poziom trudności

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

Rysunek 1. Scenariusz wykorzystania całej rodziny produktów SQL Server

Page 47: SDJ Extra 34 Biblia

46

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 47

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

Porównanie wersjiNależy również zdawać sobie sprawę z istot-nych różnic między SQL Server Compact Edition a wersją Express. Każdy z nich ma swoje zastosowanie i żaden z nich nie wyklu-cza drugiego. Można powiedzieć, że Com-pact Edition zawiera podzbiór funkcjonal-ności, jaką daje SQL Server.

W Tabeli 1 zostały zebrane informacje do-tyczące rodziny produktów SQL Server. Pi-sząc Compact Edition, mam na myśli oby-dwie wersje (desktop oraz mobile).

Wielu programistów może być zawie-dzionych z powodu braku wsparcia dla ta-kich elementów T-SQL , jak procedury skła-dowane, widoki czy triggery. Ma to swoje

uzasadnienie w wydajności aplikacji. Funk-cje te w znaczny sposób obciążałyby zaso-by urządzenia mobilnego – dlatego też zde-cydowano się z nich zrezygnować. Jednak-że wcale nie jest powiedziane, że z pewny-mi ograniczeniami nie pojawią się w kolej-nych wersjach.

Jak zacząćZawsze najtrudniej jest rozpocząć. W tym przypadku mamy o tyle prościej, że wszyst-ko, co jest potrzebne, to Visual Studio 2008 Express oraz .NET Compact Framework 3.5. Wszystkie kreatory i komponenty są do-stępne bezpośrednio w środowisku – dzięki

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

Cecha SQL Server Compact Edition SQL Server Express Edition

Instalacja / Dystrybucja Instalacja / Dystrybucja Instalacja / Dystrybucja

Wsparcie dla ClickOnce + +

Uruchamiane razem z aplikacją + -

Wymagane prawa administratora - +

Uruchomienie na Windows Mobile + -

Możliwość dołączenia do pliku MSI + +

Działanie w procesie + -

Wsparcie dla 64-bit Dotyczy CE 3.5 w wersji Desktop z SP1- Version 3.1+ Natywnie 64 bitowy w przyszłych wersjach

+ Windows on Windows (WOW)

Działanie jako usługa - +

Pliki Pliki Pliki

Format plików Pojedyńczy - .SDF Wiele formatów

Maksymalny rozmiar bazy 4GB 4GB

Przechowywanie XML + jako nText +

Programowanie Programowanie Programowanie

Transact-SQL + +

Proceduralny T-SQL,składnia Select Case, If

- +

Remote Data Access (RDA) + -

ADO.NET Sync Framework + Dostępne z Visual Studio 2008 - Planed support with a future version

Merge replication + +

Proste transakcje + +

Rozproszone transakcje - +

Natywna obsługa XML, XQuery/QPath - +

Stored procedures, views, triggers - +

Podział na role w zabezpieczeniach - +

Ilość jednoczesnych połączeń 256 Unlimited

Rysunek 2. Architektura ADO.NET dla SQL Server CE

�������

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

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

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

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

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

Page 48: SDJ Extra 34 Biblia

48

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 49

czemu aplikację można dosłownie wyklikać. Oczywiście można, ale nie trzeba – mamy również odpowiedni zestaw klas, by same-mu łączyć się z bazą i wykonywać na niej za-pytania. Oba rozwiązania postaram się przy-bliżyć w dalszej części artykułu. Dodatko-wo można doinstalować różne narzędzia, takie jak SQL Management Studio. Istotna jest wersja tego ostatniego. Tylko najnow-sza– 2008 (również express) wspiera wersję SQL Server CE 3.5. Na płycie CD znajduje się kopia pliku bazy danych wykorzystywa-nej w artykule.

Połączenie z bazą danychOsoby dobrze znające ADO.NET będą mile zaskoczone. Wykorzystanie SQL Server CE w swojej aplikacji opiera się właśnie na tej technologii. Rysunek 2 przedstawia archi-tekturę ADO.NET dla SQL Server CE.

• SqlCeConnection – odpowiada za fi-zyczne połączenie z bazą danych. Połą-czenie wymaga odpowiedniego connec-tion stringa, w którym zawarta jest in-formacja dotycząca tego, który plik ma zostać wczytany. Może zawierać rów-nież opcjonalne atrybuty, takie jak ha-sło do bazy oraz to, czy baza jest zaszy-

frowana. Wszystkie te atrybuty zostały opisane w podrozdziale bezpieczeństwo. Najczęściej jednak connection string będzie wyglądał tak jak na Listingu 1.

• SqlCeDataAdapter - obiekty DataAdap-ter definiują, jak dane mają być przeka-zywane do obiektu DataSet i z obiek-tu DataSet. Konfiguracja obiektu Da-taAdapter polega zwykle na wskazaniu poleceń SQL stosowanych do odczy-tu, zapisu i modyfikacji danych. Każdy obiekt DataAdapter służy do wymiany danych pomiędzy jedną tabelą źródła danych a pojedynczym obiektem Da-taTable. Obiekt ten znajduje się w Da-taSet. Jeśli obiekt DataSet zawiera wie-le tabel, to najczęściej stosowanym roz-wiązaniem jest tworzenie wielu obiek-tów DataAdapter, pobierających, zapi-sujących i modyfikujących dane w poje-dynczych tabelach źródeł danych.

• SqlCeDataSet – jest to magazyn da-nych, który przechowuje w pamię-ci wszystkie rekordy uzyskane po-przez obiekt DataAdapter. Dzięki te-mu obiektowi spora ilość kodu gene-rowana jest automatycznie. Mówimy również, że DataSet jest silnie typowa-ny, gdyż wszystkie pola są przechowy-

wane zgodnie z ich typem, a nie kon-wertowane do łańcuchów znaków. Na-leży pamiętać również, że po modyfi-kacjach zbioru należy samemu wymu-sić zapisanie zmian. Odbywa się to po-przez wywołanie metody AcceptChan-ges obiektu DataSet. Ciekawą cechą te-go obiektu jest możliwość jego seria-lizacji i wysłania poprzez usługi Web Services.

• SqlCeCommand – reprezentuje zapy-tanie, które jest wykonywane bezpo-średnio na pliku bazy. Zapytaniem może być insert, select lub update. Nie-stety, jak wspomniałem w poprzed-niej części artykułu, nie ma możliwo-ści wykonywania procedur składowa-nych. Cała składnia oprócz wcześniej wspomnianych konstrukcji jest zgod-na z t-sql. Najczęściej instancja tego obiektu tworzona jest poprzez wyko-nanie metody ExecuteReader obiektu SqlCeConnection.

• Data Consumer – jest to każdy obiekt, który wykorzystuje dane do prezenta-cji lub dalszego ich przetwarzania.

• SqlCeResultSet, który nie został pokaza-ny na obrazku, jest połączeniem funk-cjonalności DataSet – modyfikacje da-nych oraz SqlCeDataReader. W przy-padku DataSet w pamięci tworzona jest kopia danych zawartych w bazie. Dla dużych baz może to przysporzyć kłopo-tu. SqlCeResultSet operuje bezpośred-nio na fizycznym pliku sdf, dzięki cze-mu nie pobiera tyle pamięci. Instancja obiektu tworzona jest poprzez wywo-łanie metody ExecuteResultSet obiektu SqlCeCommand.

Tworzenie bazy może odbywać się poprzez kreator. Aby to zrobić, należy kliknąć pra-wym przyciskiem na nasz projekt w oknie

Listing 1. Generyczny connection string do bazystring connStr = "data source=\secure.sdf;"

Listing 2. Ścieżka do pliku dla connection stringa obiektu SqlCeConnection

("Data Source =" + (System.IO.Path.GetDirectoryName(

System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) +

"\\bazadanych.sdf;"));

Listing 3. Szablonowe zapytanie z parametrem

SELECT * from Customers where [Customer ID] = @custID

Listing 4. Wykonanie szablonu zapytania

customersTableAdapter1.GetDataBy("BLONP");

Rysunek 3. Okno „Add New Item”Rysunek 4. Okno “Server Explorer” z ustanowionym połączeniem

Page 49: SDJ Extra 34 Biblia

48

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 49

Solution Explorer (domyślnie znajdującym się po prawej stronie Visual Studio). Jeśli go nie ma, z menu view wybieramy Solution Explorer, co spowoduje jego pojawienie się. Klikamy Add new item, aby dodać do pro-jektu nową bazę, lub Add existing item, je-śli chcemy dodać do projektu istniejącą ba-zę danych. W przypadku utworzenia nowej bazy wyświetli się kreator, w którym klika-my na pole Database file. Ten krok prezen-tuje Rysunek 3.

Po stworzeniu pliku należy w jego wła-ściwościach (prawy klawisz na nim w Solu-tion Explorer, Properties) upewnić się, że ma-my zaznaczoną opcję copy if never we właści-wości Copy to Output. Spowoduje to utwo-rzenie kopii naszej bazy po jej kompilacji w miejsce, gdzie będzie przechowywany plik wykonywalny naszej aplikacji. Listing 2 pre-zentuje poprawny connection string dla Sql-CeConnection.

Niestety connection string typu Data So-urce = \bazadanych.sdf nie zadziała.

Aby połączyć się z bazą i wykonywać na niej zapytania w Visual Studio, należy sko-rzystać z okna Server Explorer. Dodając po-łączenie (Add New Connection), wystarczy wybrać z Data Source – SQL Server CE 3.5, wskazać odpowiedni plik i połączenie zosta-nie ustanowione. Widok połączenia z bazą prezentuje Rysunek 4.

Użycie kreatorówWykorzystując kreatory do tworzenia po-łączeń, wypełniania komponentów dany-mi, możemy zaoszczędzić mnóstwo czasu. Aplikację można dosłownie wyklikać, gdyż dodając bazę danych do projektu, od razu kreator zapyta nas, czy wygenerować do te-go połączenia data seta. Bezpośrednio w Da-taSet można dodawać metody, które będą zawierały gotowe szablony zapytań. Aby to zrobić, należy:

• kliknąć prawym klawiszem na North-wind.sdf w Solution Explorer i wybrać Visual Designer. Możemy sprawdzić tu-taj związki między tabelami, usuwać kolumny oraz, jak wspomniałem wcze-śniej, tworzyć szablon zapytań, nadając mu swoją nazwę;

• kliknąć prawym klawiszem na Table-Adapter tabeli Customers i wybrać Add, wpisując w miejsce zapytania polecenie z Listingu 3.

Będziemy mieli możliwość wykonania te-go szablonu jakby był zwykłą metodą z pa-rametrem, którego typ jest zgodny z ty-pem kolumny [Customer ID]. Metodę mo-żemy wywołać poprzez obiekt TableAdap-ter bazy danych, i wpisując nazwę szablo-nu. Według domyślnej notacji powinien mieć nazwę customersTableAdapter1. Je-

śli nie ma takiego komponentu, należy go dodać poprzez przeciągnięcie go z ToolBo-xa(zakładka nazwa_projektu Components).

Swój szablon nazwałem GetDataBy. W ko-dzie wywołanie wygląda tak jak prezentu-je to Listing 4.

Tabela 2. Opis metod SqlCeConnection

Metoda Opis metody

BeginTransaction Rozpoczyna transakcje.

ChangeDatabase Zmiana aktualnie wykorzystywanej bazy danych.

Close Zamyka połączenie. Wszystkie dodatkowe zasoby są zwalniane.

CreateCommand Tworzy obiekt SqlCeCommand.

GetSchema Zwraca całą strukturę data source.

Open Otwiera połączenie z bazą danych.

Tabela 3. Opis właściwości obiektu SqlCeConnection

Properties Property Description

ConnectionString Pobiera lub ustawia connectrion stringa.

DataBase Pobiera nazwę aktualnej otwartej bazy danych.

DataSource Pobiera całą ścieżkę do fizycznego pliku bazy danych.

ServerVersion Zwraca wersję Sql Servera CE.

State Zwraca stan, w jakim znajduje się połączenie.

Tabela 4. Opis metod obiektu SqlCeCommand

Metoda Opis metody

Cancel Anuluje wykonanie aktualnie przetwarzanego zapytania.

CreateParameter Tworzy nowy parametr.

ExecuteNonQuery Wykonuje zapytanie do bazy i zwraca ilość zmodyfikowanych wierszy.

ExecuteReader Tworzy SqlDataReadera, którym następnie możemy czytać wiersze zwrócone z zapytania.

ExecuteScalar Wykonuje zapytanie i zwraca pierwszą kolumnę pierwszego wiersza zwróconych przez zapytanie w zbiorze danych.

Tabela 5. Opis właściwości obiektu SqlCeCommand

Właściwość Opis

CommandText Pobiera lub ustawia tekst zapytania, które będzie wykonane.

CommandTimeOut Pobiera lub ustanawia ilość czasu oczekiwania na wykonanie zapyta-nania nim zostanie wygenerowany błąd.

CommandType Pobiera lub ustanawia, jak ma być interperetowane zapytanie. Możliwe opcje to:StoredProcedure – nie wykorzystywane w SQL Server CE.Text – kiedy przekazujemy zapytanie w formie tekstu.TableDirect—zwraca wszystkie wiersze i kolumny w tabeli. Command Text zawiera tylko nazwę tabeli, która będzie wyświetlona.

Connection Zwraca lub ustawia obiekt SqlCeConnection, który jest przypisany lub ma być przypisany do obiektu.

IndexName Definiuje index, który ma być wykorzystany.

Parameters Pobiera listę kolekcji parametrów (SqlCeParameterCollection).

Transaction Pobiera lub ustawia transakcję, która będzie wykonana.

Tabela 6. Opis metod SqlCeDataReader

Metoda Opis metody

Close Zamyka DataReadera.

GetFieldType Pobiera typ pola dla określonej kolumny.

GetName Pobiera nazwę określonej kolumny.

GetSchemaTable Zwraca strukturę tebeli.

GetValue Zwraca wartość kolumny.

IsDBNull Zwraca true, false w zależności, czy typ jest Nullem.

Read Pobiera kolejny record.

Seek Przechodzi do wierszy określonych w parametrze.

Page 50: SDJ Extra 34 Biblia

50

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 51

Bindowanie danych również opiera się na kreatorach, które zaoszczędzają nam czas. We właściwościach prawie każdego kompo-nentu znajdziemy Data Binding.

Własne zarządzanie danymiAby móc wykorzystywać poniższe klasy, na-leży do sekcji uses dodać przestrzeń nazw System.Data.SqlServerCe; klasy te mają peł-ną funkcjonalność zapytań t-sql zgodnych z SQL Server CE 3.5 i to programista ma pełną kontrolę nad parametrami przekazy-wanymi do bazy danych. Aby połączyć się z bazą, należy wykorzystać do tego SqlCeCon-nection. W Tabeli 2 oraz Tabeli 3 znajduje się opis najczęściej wykorzystywanych metod tej klasy oraz jej właściwości.

Po połączeniu chcielibyśmy wykonać za-pytanie na tabeli. Wykorzystamy do tego klasę SqlCeCommand. W Tabeli 4 oraz Ta-beli 5 znajduje się opis najczęściej wyko-rzystywanych metod tej klasy oraz jej wła-ściwości.

SqlCeDataReader służy do odczytu da-nych, które zostały zwrócone przez zapyta-nie. Dzięki niemu możemy przemieszczać się po całym zbiorze, odczytując wartości z określonych kolumn. W Tabeli 6 oraz Tabe-li 7 znajduje się opis najczęściej wykorzysty-wanych metod oraz właściwości klasy SqlCe-DataReader.

Po wstępie teoretycznym możemy przejść do praktycznego zastosowania opisanych klas. Wszystkie operacje będą dotyczyły ba-zy danych Northwind w pliku Northwind.sdf załączonego na płycie CD.

Przykład 1. Uniwersalna metoda, która czyta dowolny typ, znajduje się w Listin-gu 5.

Kolejny przykład będzie demonstrował zapytanie wstawiające dane(insert). Wyko-rzystana zostanie klasa sqlParameters. Ce-chuje ją uniwersalność zastosowania – nie musimy przejmować się np. czy użytkownik podczas wstawiania do pola typu decimal (10,2) wstawi jako znak oddzielający część ułamkową od części całych kropkę czy prze-cinek. Sam parametr zostanie „dopasowany” do odpowiedniego typu, tak aby był zgod-ny z typem pola tabeli. Oczywiście zdarza się, że użytkownik wprowadzi takie dane, że dopasowanie nie będzie możliwe. Wtedy zgłoszony zostanie wyjątek. Listing 6 zawie-ra kod, który odczytuje z bazy danych Tabe-lę employees i umieszcza odczytane wartości w generycznym kontenerze.

Przykład 2. Update do bazyZa sprawą tego, że baza danych jest na

urządzeniu mobilnym, nie mamy możli-wości przeglądania jej przez Server Explorer w Visual Studio. Może to być trochę mylą-ce, ponieważ każda modyfikacja bazy przy użyciu IDE Visual Studio będzie miała od-zwierciedlenie w bazie na urządzeniu mo-

Tabela 7. Opis właściwości obiektu SqlCeDataReader

Właściwość Opis właściwości

Depth Zwraca głębokość zagnieżdżonego wiersza. Dotyczy podzapytań.

FieldCount Zwraca ilość kolumn w aktualnym wierszu.

HasRows Zwraca wartość true lub false w zależności, czy baza na skutek zapy-tania zwróciła jakiś record.

HiddenFieldCount Zwraca ilość ukrytych pól.

IsClosed Sprawdza, czy Reader jest zamknięty.

RecordsEffected Zwraca ilość wierszy, ile zostało zmodyfikowanych po wykonaniu za-pytania insert, delete, update.

VisibleFieldCount Zwraca ilość pól, które nie są ukryte.

Listing 5. Metoda łączenia się z bazą danych

try

{

SqlCeConnection conn = new SqlCeConnection("Data Source =" +

(System.IO.Path.GetDirectoryName(

System.Reflection.Assembly.GetExecutingAssembly().GetName().CBase) +

"\\Northwind.sdf;")); //główne połączenie z bazą danych

SqlCeCommand cmd = new SqlCeCommand("Employees", conn);

cmd.CommandType = CommandType.TableDirect;// zapytanie jest nazwą tabeli

conn.Open();//otwórz połączenie

SqlCeDataReader rdr = cmd.ExecuteReader();//utwórz instancję DataReadera

string infoText = "Wersja Sql Servera CE - " + conn.ServerVersion + "/n";

infoText += "Nazwa bazy " + conn.Database + "/n";

MessageBox.Show(infoText);

if (rdr.HasRows)

{

List<object> Employees = new List<object>();//Generyczna lista obiektów

while (rdr.Read())

{

var Employee = new

{

Nazwisko = rdr["Last Name"].ToString(),

Imię = rdr["First Name"].ToString()

};//Typ anonimowy

Employees.Add(Employee);//nasz kontener pracowników

}

}

else

MessageBox.Show("Brak rekordów do wyświetlenia");

}

catch(Exception E)

{

MessageBox.Show("Błąd podczas połączenia! Zwrócony został komunikat:

"+E.Message);

}

Tabela 8. Zestawienie zbioru bibliotek dll oraz ich funkcji

Nazwa biblioteki Funkcjonalność

sqlcese35.dll Silnik przechowywania danych (ang. Storage Engine)

sqlceqp35.dll Procesor zapytań (ang. Query Processor)

sqlceme35.dll Native / Managed Translation Layer

System.Data.SqlServerCe.dll Provider do ADO.NET

sqlcecompact35.dll APIs do kompresji i uaktualnień

sqlceca35.dll API dla Merge Replication oraz RDA

sqlceoledb35.dll OleDB API – potrzebne dla C++, VB, oraz Merge/RDA

sqlceer35EN.dll Przechowuje nazwy błędów

Page 51: SDJ Extra 34 Biblia

50

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 51

bilnym. Z kolei po modyfikacji bazy na urzą-dzeniu mobilnym nie będzie śladu w Visu-al Studio.

Załóżmy, że chcemy usunąć Klienta z Ta-beli Customers. Jednak aby to zrobić, musi-my usunąć najpierw jego zamówienia (Ta-bela Orders). Aby to zrobić, musimy usunąć pozycję z Tabeli zagnieżdżonej Order Deta-ils. Przy tych trzech zapytaniach wykonywa-nych po sobie może dojść do błędu i nie całe zadanie zostanie wykonane. Będzie to kata-strofalne w skutkach, gdyż pojawią się prze-kłamania w bazie, np. wszystkie szczegóły dotyczące zamówienia zostaną usunięte, a pozostanie informacja o istnieniu zamówie-nia. Podczas odczytywania danych okaże się, że kwota zamówienia będzie równa 0. Dla-tego najlepszym rozwiązaniem jest wprowa-dzenie transakcji. W przypadku błędu przy wykonaniu któregokolwiek z zapytań, au-tomatycznie zostanie przywrócona wersja sprzed rozpoczęcia operacji.

Przykład 3. Wykorzystanie SqlCeTransact

Synchronizacja Funkcjonalność SQL Server CE nie ograni-cza się tylko do lokalnej bazy danych. Moż-na również zastosować tzw. replikację, któ-ra odbywa się za pośrednictwem Internetu. Replikacja umożliwia synchronizację lokal-nej bazy danych (na urządzeniu mobilnym) oraz globalnej (dostępnej np. w firmie). Wy-obraźmy sobie sytuację, w której przedsta-wiciel handlowy zbiera zamówienia poza firmą. Oczywiście chcielibyśmy, aby infor-macje o aktualnych zamówieniach były do-stępne również w firmie, dla której pracu-je. Jednym z rozwiązań może być wykorzy-stanie usług Web Services do uaktualnienia bazy danych zawartej na serwerze. Jednak co jeśli w danej chwili przedstawiciel handlowy nie ma dostępu do Internetu? Jednym z roz-wiązań jest właśnie replikacja, dzięki której przedstawiciel zbiera informację do swojej lokalnej bazy danych. Z kolei kiedy ma do-stęp do Internetu, może wysłać uaktualnie-nie do bazy głównej. Działa to w obie stro-ny, gdyż poprzez główną bazę danych można uaktualniać informację dla przedstawiciela handlowego. Ilustruje to Rysunek 4.

Przy czym główną bazą danych musi być SQL Server w dowolnej wersji. Istnieją 4 me-tody synchronizacji SQL Server z SQL Se-rver CE 3.5:

• usługi Web Services;• Remote Data Access;• Merge Replication;• Sync Services for ADO.NET.

Dystrybucja oprogramowaniaSQL Server Compact Edition został za-projektowany z myślą o łatwej dystrybu-cji. Urządzenia takie jak Pocket PC nie ma-

ją skomplikowanego systemu instalacji, a co więcej nie dysponują dużymi zasoba-mi sprzętowymi. Ponieważ SQL Server CE jest hostowany w aplikacji, niepotrzebne są żadne pliki konfiguracyjne czy uruchamia-nie dodatkowych usług. Jedynym źródłem konfiguracji jest connection string określają-cy, jak aplikacja ma się łączyć z plikiem bazy danych. W najprostszym przypadku, kompi-lując projekt do wersji release , Visual Stu-

dio skompiluje nam nasze rozwiązanie do trzech plików. Kompilacja do wersji release odbywa się poprzez wybranie z menu Build � Build Solution. Oczywiście z wybraną opcją release w zakładce Build właściwości solucji. Wszystkie pliki przy domyślnych ustawie-niach powinny znajdować się w katalogu na-zwa solucji\bin\Release. Na te trzy pliki skła-da się baza danych, program exe gotowy do uruchomienia bezpośrednio na urządzeniu

Listing 6. Przykład zastosowania zapytania Insert

try

{

SqlCeConnection conn = new SqlCeConnection("Data Source =" +

(System.IO.Path.GetDirectoryName(

System.Reflection.Assembly.GetExecutingAssembly().GetName().Code

Base) +

"\\Northwind.sdf;"));//połączenie z bazą danych

string[,] Categories = { { "Drinks", "Cola, Pepsi" },

{ "Tea", "White Tea, Black Tea" },

{ "Alcohols", "Bear, Vine" } };

//lista kategorii, które będziemy wstawiać

// nasze zapytanie posiada 2 parametry, które w czasie wykonania będą dodawane

dynamicznie

SqlCeCommand cmd = new SqlCeCommand(@"INSERT INTO Categories

([Category Name], Description, Picture)

VALUES

(@categoryName,@description,null)", conn);

int count = 0;

conn.Open();

for(int i=0;i<Categories.GetLength(0);i++)

{

cmd.Parameters.Clear();//wyczyść poprzednie parametry

cmd.Parameters.AddWithValue("@categoryName", Categories[i, 0]);

cmd.Parameters.AddWithValue("@description", Categories[i, 1]);

count += cmd.ExecuteNonQuery();//wykonaj i uaktualnij listę dodanych

rekordów

}

MessageBox.Show("Wstawionych rekordów - " + count);

}

catch (Exception E)

{

MessageBox.Show("Błąd podczas połączenia! Zwrócony został komunikat: " +

E.Message);

}

}

Tabela 9. Wyniki dla Desktop

Linq to XML Access 2007 Sql Server CE

Nazwa pliku data.xml data.accdb data.sdf

Rozmiar pliku Przed insertem Nie dotyczy 280KB

20KB Po insercie 333 KB480KB224KB

Zapytanie Create table Nie dotyczy 0,4064 sek. 0,1795 sek.

Zapytanie Insert(1014 wierszy) 5,2366 sek. 4,0495 sek. 1,0396 sek.

Zapytanie Select 0,0305 sek. 0,0664 sek. 0,0413 sek.

Zapytanie Delete Nie dotyczy 0,0766 sek. 0,1184 sek.

Page 52: SDJ Extra 34 Biblia

52

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 53

mobilnym oraz dodatkowy plik wykorzysty-wany przez Visual Studio do Debuggingu.

Jak już wspomniałem wcześniej, SQL Se-rver CE 3.5 fizycznie jest zbiorem biblio-tek dll. Ich nazwy oraz opis prezentuje Ta-bela 8.

BezpieczeństwoZ racji tego, że nasza aplikacja składa się z pliku wykonywalnego i pliku bazy danych naturalnie nasuwa się pytanie dotyczące bezpieczeństwa tego rozwiązania. Przecież każdy ma dostęp do tego pliku i każdy przy

Listing 7. Przykładowe zapytania usuwające dane

SqlCeConnection conn = null;

SqlCeTransaction trans = null;

try

{

conn = new SqlCeConnection("Data Source =" +

(System.IO.Path.GetDirectoryName(

System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) +

"\\Northwind.sdf;"));//połączenie z bazą danych

conn.Open();

trans = conn.BeginTransaction();

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

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

from Orders where [Customer ID] in

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

cmdOrdDet.Transaction = trans;

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

SqlCeCommand cmdOrd = new SqlCeCommand(@"delete from Orders where [Customer ID] in

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

cmdOrd.Transaction = trans;

//usunięcie klienta z Wenezueli

SqlCeCommand cmdCust = new SqlCeCommand(@"delete from Customers where

Country = N'Venezuela'", conn);

cmdCust.Transaction = trans;

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

count += cmdOrd.ExecuteNonQuery();//wykonaj zapytanie na Order

count += cmdCust.ExecuteNonQuery();//wykonaj zapytanie na Customers

MessageBox.Show("Liczba usuniętych rekordów - " + count);

trans.Commit();//jeśli bez błędu, to zaakceptuj zmiany

}

catch (Exception E)

{

if (trans != null)

trans.Rollback();//jesli błąd, to cofnij zmiany

MessageBox.Show("Błąd podczas połączenia! Zwrócony został komunikat: " + E.Message);

}

finally

{

if (conn != null && conn.State == ConnectionState.Open)

conn.Close();//jeśli połączenie otwarte, to je zamknij

}

Tabela 10. Wyniki dla PocketPC

Linq to XML Sql Server CE

Rozszerzenie pliku data.xml data.sdf

Rozmiar pliku Przed insertem Nie dotyczy

20KB Po insercie333 KB224KB

Zapytanie Create table Nie dotyczy 2,4569 sek.

Zapytanie Insert(1014 wierszy) 760,9975 sek. 27,9072 sek.

Zapytanie Select 2,1460 sek. 0,3091 sek.

Zapytanie Delete Nie dotyczy 0,3702 sek.

Page 53: SDJ Extra 34 Biblia

52

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 53

użyciu ogólnodostępnych narzędzi może odczytać dane zawarte w bazie. Oczywiście wszystko zależy od tego, czy dane są jawne. Jeśli tak, to nie musimy się martwić. Co jed-nak, kiedy dane są poufne? Microsoft prze-widział również taki scenariusz. Plik bazy danych można zabezpieczyć hasłem lub za-szyfrować. Oczywiście programista nie musi robić wszystkiego ręcznie, a jedynie podczas tworzenia bazy zaznaczyć opcję szyfrowania

pliku lub zabezpieczenia hasłem. Wszyst-kie zapytania można wykonywać poprzez obiekt SqlCeEngine.CreateDatabase w con-nection stringu, wpisując odpowiednie po-lecenie tworzące. Listing 8 zawiera zapyta-nie, które tworzy plik bazy danych zabezpie-czony hasłem.

Z kolei stworzenie zaszyfrowanej oraz zabezpieczonej hasłem bazy prezentuje Li-sting 9.

Podczas połączenia w connection stringu należy wprowadzić dodatkowe atrybuty. Li-sting 10 prezentuje szablonowy connection string dla połączenia z zaszyfrowaną i zabez-pieczoną hasłem bazą danych.

Tworzenie bazy zabezpieczonej jedynie hasłem prezentuje Listing 11.

Daje to dodatkowy poziom bezpieczeń-stwa bez obciążania programisty implemen-tacją ręcznego szyfrowania. Niestety w tym

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

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

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

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

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

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

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

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

Listing 12. Metody XMLInsert oraz XMLSelect

private void XMLInsert()

{

if (File.Exists("data.xml"))

File.Delete("data.xml");

XDocument xdoc = new XDocument(new XElement("Orders"));

foreach(Order order in Orders)

{

xdoc.Element("Orders").Add(new XElement("Order",

new XAttribute("OrderID",order.OrderID.ToString()),

new XAttribute("CustomerID", order.CustomerID == null ? "NULL" : order.CustomerID.ToString()),

new XAttribute("EmployeeID", order.EmployeeID.ToString()),

new XAttribute("ShipName", order.ShipName == null ? "NULL" : order.ShipName.ToString()),

new XAttribute("ShipAddress", order.ShipAddress == null ? "NULL" : order.ShipAddress.ToString()),

new XAttribute("ShipCity", order.ShipCity == null ? "NULL" : order.ShipCity.ToString()),

new XAttribute("ShipRegion", order.ShipRegion == null ? "NULL" : order.ShipRegion.ToString()),

new XAttribute("ShipPostalCode", order.ShipPostalCode == null ? "NULL" : order.ShipPostalCode.ToString()),

new XAttribute("ShipCountry", order.ShipCountry == null ? "NULL" : order.ShipCountry.ToString()),

new XAttribute("shipVia", order.ShipVia.ToString()),

new XAttribute("OrderDate", order.OrderDate.ToString()),

new XAttribute("RequireDate", order.RequiredDate.ToString()),

new XAttribute("ShippedDate", order.ShippedDate.ToString()),

new XAttribute("Freight", order.Freight == null ? "NULL" : order.Freight.ToString())));

xdoc.Save("data.xml");

}

}

private void XMLSelect()

{

if (File.Exists("data.xml"))

{

XDocument doc = XDocument.Load("data.xml");

var query = from c in doc.Elements("Orders") select c;

foreach (var item in query)

{

item.Elements("Order");

}

}

}

Page 54: SDJ Extra 34 Biblia

54

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 55

przypadku nie ma podziału na role, dlatego jeśli ktoś zna connection stringa, ma dostęp do całej bazy. Jednak dla większości scena-riuszy jest to rozwiązanie wystarczające.

WydajnośćTestując wydajność, zestawiłem trzy podob-ne do siebie rozwiązania. Wszystkie zostały stworzone do tego samego celu – przecho-wywania informacji. Pierwszym z nich jest Linq to XML, który dostępny jest w .NET 3.5 oraz .NET CF 3.5. Najwięcej proble-mów sprawiła mi właśnie technologia LINQ to XML. Nie z powodu tego, że technologia ta jest trudna w implementacji, lecz z powo-du problemów z odpowiednim odniesie-niem jej do pozostałych technologii. Gene-ralnie cały problem pojawił się podczas za-pisywania danych. Otóż klasa XDocument służąca do manipulowania danymi tworzy

Tabela 11. Zestawienie plusów i minusów rozwiązania

Plusy Minusy

Obsługa transakcji Brak podziału na użytkowników

Obsługa replikacji Brak perspektyw, procedur składowa-nych oraz Triggerów

Możliwość wizualne-go projektowania ba-zy danych

Zgodność z ADO.NET

128 bitowe zabezpie-czenia

Procesor zapytań

Łatwe bindowanie danych

Możliwość zdefinio-wania funkcji w Da-ta Set

Łatwa do przeniesie-nia baza danych

Listing 13. Metody do tworzenia, wstawiania, wyświetlania i usuwania danych w bazie Access 2007

private void CreateAccessDatabase()

{

OleDbConnection conn = new OleDbConnection(AccessConn);

OleDbCommand cmd = new OleDbCommand(cmdCreateTabStr, conn);

conn.Open();

cmd.ExecuteNonQuery();

conn.Close();

}

private void InsertToAccess()

{

OleDbConnection conn = new OleDbConnection(AccessConn);

OleDbCommand cmd = new OleDbCommand(cmdInsertTo, conn);

conn.Open();

foreach(Order order in Orders)

{

cmd.Parameters.Clear();

cmd.Parameters.AddWithValue("@OrderID", order.OrderID);

cmd.Parameters.AddWithValue("@CustomerID",order.CustomerID);

cmd.Parameters.AddWithValue("@EmployeeID",order.EmployeeID);

cmd.Parameters.AddWithValue("@ShipName",order.ShipName);

cmd.Parameters.AddWithValue("@ShipAddress",order.ShipAddress);

cmd.Parameters.AddWithValue("@ShipCity",order.ShipCity);

cmd.Parameters.AddWithValue("@ShipRegion",order.ShipRegion);

cmd.Parameters.AddWithValue("@ShipPostalCode",order.ShipPostalCode);

cmd.Parameters.AddWithValue("@ShipCountry",order.ShipCountry);

cmd.Parameters.AddWithValue("@ShipVia",order.ShipVia);

cmd.Parameters.AddWithValue("@OrderDate",order.OrderDate);

cmd.Parameters.AddWithValue("@RequiredDate",order.RequiredDate);

cmd.Parameters.AddWithValue("@ShippedDate",order.ShippedDate);

cmd.Parameters.AddWithValue("@Freight",order.Freight);

foreach (OleDbParameter parameter in cmd.Parameters)

{

if (parameter.Value == null)

{

parameter.Value = DBNull.Value;

}

}

cmd.ExecuteNonQuery();

}

conn.Close();

}

private void SelectAccess()

{

OleDbConnection conn = new OleDbConnection(AccessConn);

OleDbCommand cmd = new OleDbCommand(cmdSelectFrom, conn);

conn.Open();

OleDbDataReader rdr = cmd.ExecuteReader();

while (rdr.Read())

{

;

}

conn.Close();

}

private void DeleteFromAccess()

{

OleDbConnection conn = new OleDbConnection(AccessConn);

OleDbCommand cmd = new OleDbCommand(cmdDelete, conn);

conn.Open();

cmd.ExecuteNonQuery();

conn.Close();

}

Rysunek 4. Schemat bazy danych dla przykładowej firmy

Page 55: SDJ Extra 34 Biblia

54

Programowanie Windows Mobile

SDJ Extra 34 Biblia

MS SQL Server CE 3.5

www.sdjournal.org 55

Listing 14. Metody analogiczne do bazy Access zaimplementowane dla SQL Server CE

private void CreateSQLDatabase()

{

SqlCeConnection conn = new SqlCeConnection(SqlConn);

SqlCeCommand cmd = new SqlCeCommand(cmdCreateTabStr, conn);

conn.Open();

cmd.ExecuteNonQuery();

conn.Close();

}

private void InsertToSQL()

{

SqlCeConnection conn = new SqlCeConnection(SqlConn);

SqlCeCommand cmd = new SqlCeCommand(cmdInsertTo, conn);

conn.Open();

foreach (Order order in Orders)

{

cmd.Parameters.Clear();

cmd.Parameters.AddWithValue("@OrderID", order.OrderID);

cmd.Parameters.AddWithValue("@CustomerID", order.CustomerID);

cmd.Parameters.AddWithValue("@EmployeeID", order.EmployeeID);

cmd.Parameters.AddWithValue("@ShipName", order.ShipName);

cmd.Parameters.AddWithValue("@ShipAddress", order.ShipAddress);

cmd.Parameters.AddWithValue("@ShipCity", order.ShipCity);

cmd.Parameters.AddWithValue("@ShipRegion", order.ShipRegion);

cmd.Parameters.AddWithValue("@ShipPostalCode", order.ShipPostalCode);

cmd.Parameters.AddWithValue("@ShipCountry", order.ShipCountry);

cmd.Parameters.AddWithValue("@ShipVia", order.ShipVia);

cmd.Parameters.AddWithValue("@OrderDate", order.OrderDate);

cmd.Parameters.AddWithValue("@RequiredDate", order.RequiredDate);

cmd.Parameters.AddWithValue("@ShippedDate", order.ShippedDate);

cmd.Parameters.AddWithValue("@Freight", order.Freight);

foreach (SqlCeParameter parameter in cmd.Parameters)

{

if (parameter.Value == null)

{

parameter.Value = DBNull.Value;

}

}

cmd.ExecuteNonQuery();

}

conn.Close();

}

private void SelectSQL()

{

SqlCeConnection conn = new SqlCeConnection(SqlConn);

SqlCeCommand cmd = new SqlCeCommand(cmdSelectFrom, conn);

conn.Open();

SqlCeDataReader rdr = cmd.ExecuteReader();

while (rdr.Read())

{

;

}

conn.Close();

}

private void DeleteFromSQL()

{

SqlCeConnection conn = new SqlCeConnection(SqlConn);

SqlCeCommand cmd = new SqlCeCommand(cmdDelete, conn);

conn.Open();

cmd.ExecuteNonQuery();

conn.Close();

}

całą strukturę w pamięci, a następnie ca-łość jest zapisywana na dysk. W przypadku pozostałych technologii każda operacja jest niezależna. Oznacza to, że błąd w wykona-niu danej operacji nie spowoduje utraty da-nych zmodyfikowanych we wcześniejszych operacjach. W przypadku LINQ to XML po wystąpieniu błędu utracone zostaną wszyst-kie dane. Dlatego zdecydowałem się na dra-styczny krok i po każdym dodanym wierszu odbędzie się aktualizacja pliku. Spowodowa-ło to bardzo duże opóźnienia, jednak uwa-żam, że takie rozwiązanie jest najbardziej sprawiedliwe.

Kolejną technologią, jaką wykorzystałem w artykule, jest połączenie z bazą danych silnika Microsoft Access 2007. Wszystko działa również w postaci jednego pliku ac-cdb. Do połączenia wykorzystuję technolo-gię OleDB.

W końcu ostatnie rozwiązanie – SQL Se-rver CE. Do połączenia wykorzystuje prze-strzeń nazw System.Data.SqlServerCe.

W przypadku PocketPC mamy do wybo-ru tylko dwie technologie: Linq To XML oraz SQL Server CE. Aplikacja została prze-testowana na emulatorze Windows Mobile 6.0 Professional.

Aplikacja, którą testowałem rozwiązania, dostępna jest na załączonej płycie CD. Jak widać, najszybszym rozwiązaniem jest SQL Server CE 3.5.

PodsumowaniePodsumowując, SQL Server CE 3.5 ma bar-dzo szerokie zastosowanie, zarówno dla ma-łych, jak i średnich aplikacji pisanych na platformę Windows Mobile. Obsługa T-SQL powinna szczególnie spodobać się oso-bom, które swoje aplikacje pisały już w opar-ciu o pełną wersję SQL Server. Dodatkowo możliwość wizualnego projektowania bazy danych bardzo upraszcza i przyspiesza two-rzenie aplikacji. Dzięki wprowadzeniu za-bezpieczeń bazy danych oraz jej szyfrowania aplikacja jest dobrze zabezpieczona przed atakami. Na pewno dużym minusem jest brak wsparcia dla procedur składowanych, perspektyw oraz triggerów. Zestawienie plu-sów i minusów prezentuje Tabela 11.

ŁUKASZ STRĄKŁukasz Strąk jest Microsoft Student Partnerem na Uniwersytecie Śląskim w Katowicach. Prowadzi tam lokalną grupę .NET, która zrzesza pasjona-tów technologii Microsoft. Jest również studen-tem III roku Informatyki na tamtejszym Uniwersy-tecie. Szczególnie interesuje się technologiami in-ternetowymi, programowaniem urządzeń mobil-nych, VSTO oraz WPF.Kontakt z autorem: [email protected]

Page 56: SDJ Extra 34 Biblia

56

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Moc pod kontrolą

www.sdjournal.org 57

Odrobina teoriiUrządzenia z Windows Mobile 6 z punktu widzenia SDK można podzielić na:

• Windows Mobile Professional (odpo-wiada to w WM5 urządzeniom typu PocketPC – wyposażonym w ekran do-tykowy);

• Windows Mobile Standard (odpowiada to w WM5 urządzeniom typu Smart-phone – bez ekranu dotykowego).

Każde z urządzeń z Windows Mobile na po-kładzie oprócz wyświetlacza i klawiatury mo-że zawierać szereg różnych peryferiów rozsze-rzających możliwości urządzenia: moduł GPS, moduł WLAN, Bluetooth, których działanie również może powodować szybsze zużycie ba-terii. I o ile standardowo w urządzeniach moż-na kontrolować jasność i czas działania pod-świetlenia ekranu (co jest dla baterii chyba naj-większym zadaniem), o tyle do kontrolowania stanu działania dodatkowych modułów ko-nieczne są zewnętrzne aplikacje.

Z uwagi na znaczne różnice w zarządzaniu energią między Windows Mobile Standard a Professional, poszczególne modele zostaną opisane osobno:

Model zarządzania energią w Windows Mobile Standard jest prosty. Urządzenie jest cały czas w trybie „ON”. Użycie przy-cisku ON/OFF urządzenia spowoduje je-go całkowite wyłączenie i niemożność np. prowadzenia rozmowy telefonicznej. Je-dynym sposobem na oszczędzenie baterii jest zmniejszenie jasności podświetlenia ekranu, a następnie jego wyłączenie oraz zmniejszenie prędkości działania proceso-ra. Wszystkie uruchomione wcześniej apli-kacje i wątki działają w tym stanie.

W przypadku urządzeń Windows Mobi-le Professional system zarządzania energią działa w dwóch płaszczyznach:

• systemu operacyjnego (System Power States);

• peryferiów urządzenia (Device Driver Power States).

Dla Windows Mobile Professional dostępne są następujące wartości System Power States:

• On – tryb pełnego zasilania;• BacklightOff – tryb wyłączenia pod-

świetlenia ekranu;• ScreenOff – tryb wyłączenia ekranu;• Unattended – tryb wyłączenia ekranu,

podświetlenia ekranu oraz urządzeń au-dio. Uruchomione aplikacje nadal działa-ją w urządzeniu, jednak z punktu widze-nia użytkownika urządzenie jest nieak-tywne;

• Resuming – tryb „budzenia się”. Pod-świetlenie i ekran są wyłączone, aplika-cje mają 15 sekund na przełączenie urzą-dzenia w inny stan zasilania, w przeciw-nym przypadku urządzenie przejdzie w stan wstrzymania;

• Suspended – tryb zatrzymania urządze-nia (w tym pracy procesora). Wyjście z tego stanu może zostać dokonane przez zdarzenie pochodzące od układu peryfe-ryjnego (np. moduł GSM);

Moc pod kontrolą

Odwiecznym problemem i wyzwaniem dla konstruktorów urządzeń mobilnych była możliwość ich długiej pracy z wykorzystaniem zasilania bateryjnego. Cel ten został w dużej mierze osiągnięty, ale mimo wszystko źle napisana aplikacja potrafi zmusić użytkownika do częstszego używania ładowarki.

Dowiesz się:• O zarządzaniu energią w urządzeniach Win-

dows Mobile;• Jak tworzyć aplikacje i nie powodować nad-

miernego zużycia;• Jak kontrolować tryby zasilania urządzeń

Windows Mobile.

Powinieneś wiedzieć:• Jak tworzyć aplikacje mobilne dla Windows

Mobile z wykorzystaniem Visual Studio• Znać język C#• Nie zaszkodzi znajomość wywołania metod

Win API za pomocą mechanizmu

Poziom trudności

Efektywne zarządzanie zasilaniem w aplikacjach dla systemu Windows Mobile

Listing 1. Nieskończona pętla

while(true)

{

// Działanie wątku

if (bBatteryDeadYet) {

break;

}

if (EndOfThread) {

break;

}

}

Listing 2. Nieskończona pętla z opóźnieniem czasowym

while(true)

{

//odczekaj 100ms i daj odpocząć

procesorowi

System.Threading.Thread.Sleep(100);

// Działanie wątku

if (bBatteryDeadYet) {

break;

}

if (EndOfThread) {

break;

}

}

Page 57: SDJ Extra 34 Biblia

56

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Moc pod kontrolą

www.sdjournal.org 57

• Off – urządzenie jest wyłączone – działa je-dynie podtrzymanie pracy układu zegara.

Każde z peryferiów urządzenia może przyj-mować stany zasilania:

• DO – pełne zasilanie urządzenia;• D1 – urządzenie działa w trybie oszczę-

dzania energii;• D2 – urządzenie w stanie czuwania;• D3 – urządzenie w stanie wstrzymania;• D4 – urządzenie całkowicie odłączone

od zasilania.

Dobre praktyki programowaniaJako programista masz wielki wpływ na efek-tywność wykorzystania baterii w swoich apli-kacjach. Oto kilka rad, na co należy zwrócić uwagę.

Nieskończone pętleNajczęstszą konstrukcją odpowiedzialną za wykonywanie wątku jest działanie aplika-cji w pętli.

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-ło pętli wstawić niewielkie opóźnienie w celu zmniejszenia aktywności procesora.

Aktywność timer’ów w zależności od stanu aplikacjiZdarza się, że w aplikacji używane są timer’y, których zadaniem jest periodyczne wykonywa-nie czynności w aplikacji podczas jej działania., np. odświeżanie danych w polu tekstowym. Je-śli nasza aplikacja jest w danym momencie nie-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-ku wstrzymać działanie timer’a i przywrócić je, gdy aplikacja przejdzie na „pierwszy plan”. Z pomocą przychodzą tu zdarzenia Activate i De-activate klasy System.Windows.Forms.Form. Na-leży też oczywiście pamiętać o prawidłowym zwolnieniu obiektu podczas zamykania aplika-cji (w zdarzeniu Closing).

Cykliczne wywoływanie aplikacjiJeśli celem działania Twojej aplikacji jest okresowe sprawdzanie pewnych warun-ków, np. terminarza, lokalnej bazy danych, a przez pozostałe 99% czasu się „nudzi”, to zastanów się nad wykorzystaniem funk-cji CeRunAppAtTime(string application,

SystemTime startTime) z CoreDll.dll. Funk-cję tę należy wywołać z poziomu kodu zarzą-dzanego za pomocą mechanizmu P/Invoke.

Możliwe jest również automatyczne 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-

ko parametry przyjmuje ścieżkę do pliku wy-konywalnego, oraz identyfikator zdarzenia uruchamiającego aplikację. Oto lista dostęp-nych zdarzeń:

• NOTIFICATION _ EVENT _ TIME _ CHANGE – zmiana czasu;

• NOTIFICATION _ EVENT _ SYNC _ END – zakończenie synchronizacji z PC;

Listing 3. Efektywne wykorzystanie obiektu klasy Timer

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

namespace TimerAppState

{

public partial class Form1 : Form

{

Timer t;

public Form1()

{

InitializeComponent();

}

private void Form1_Load(object sender, EventArgs e)

{

t = new Timer();

t.Interval = 1000;

t.Tick += new EventHandler(t_Tick);

t.Enabled = true;

}

void t_Tick(object sender, EventArgs e)

{

lblTime.Text = DateTime.Now.ToShortDateString() + "" + DateTime.Now.To

LongTimeString();

}

private void Form1_Closing(object sender, CancelEventArgs e)

{

t.Tick -= t_Tick;

t.Enabled = false;

t.Dispose();

}

private void Form1_Deactivate(object sender, EventArgs e)

{

t.Enabled = false;

}

private void Form1_Activated(object sender, EventArgs e)

{

t.Enabled = true;

}

private void menuExit_Click(object sender, EventArgs e)

{

Close();

}

}

}

Page 58: SDJ Extra 34 Biblia

58

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Moc pod kontrolą

www.sdjournal.org 59

• NOTIFICATION _ EVENT _ ON _ AC _ POWER – podłączenie zasilania zewnętrznego;

• NOTIFICATION _ EVENT _ OFF _ AC _

POWER – odłączenie zasilania zewnętrz-nego;

• NOTIFICATION _ EVENT _ NET _ CONNECT – połączenie urządzenia z siecią;

• N O T I F I C A T I O N _ E V E N T _ N E T _

DISCONNECT – odłączenie urządzenia od sieci;

• NOTIFICATION _ EV ENT _ DEVICE _

CHANGE – włożenie/wyciągnięcie karty pamięci;

• N O T I F I C A T I O N _ E V E N T _ I R _

DISCOVERED – wykrycie urządzenia z modułem IrDA;

• N O TIFIC ATIO N _ E V E N T _ R S232 _

DETECTED – podłączenie do urządzenia poprzez port szeregowy;

• NOTIFICATION _ EVENT _ RESTORE _

END – odtworzenie systemu po operacji hard-reset;

• NOTIFICATION _ EVENT _ WAKEUP – wyj-ście urządzenia ze stanu uśpienia;

• NOTIFICATION _ EVENT _ TZ _ CHANGE – zmiana strefy czasowej;

• NOTIFICATION _ EVENT _ MACHINE _

NAME _ CHANGE – zmiana nazwy urzą-dzenia;

• NOTIFICATION _ EVENT _ NONE – usuwa skojarzenie wywołania aplikacji.

Don’t stop the applicationGdy nasza aplikacja jest uruchomiona i nie wchodzi w interakcje z użytkownikiem, to po pewnym czasie (w przypadku urządzeń Po-cketPC) system zarządzania pamięcią najpierw wyłączy podświetlenie, potem wyłączy ekran, aby na koniec wprowadzić urządzenie w tryb uśpienia. Aby przeciwdziałać temu stanowi, można skorzystać z jednej z funkcji Power API, tj. SystemIdleTimeReset(), i kasować licznik od-powiedzialny za odmierzanie czasu do uśpienia systemu operacyjnego tuż przed próbą przejścia w ten stan. Ale jak określić ten interwał ? Z po-mocą przychodzi nam rejestr systemowy, gdzie w kluczu HKLM\SYSTEM\CurrentControlSet\Control\Power przechowywane są informacje o zwłoce w wyłączeniu ekranu i urządzenia w przypadku zasilania bateryjnego i zewnętrzne-go. Należy znaleźć najmniejszą z tych warto-ści i ustawić timer w aplikacji na nieco mniej-szą wartość w celu wywołania wspomnianej już funkcji SystemIdleTimeReset().

Wyżej wymieniony sposób niestety nie za-działa, jeśli użytkownik Pocket PC wciśnie przy-cisk „On/Off”, dając sygnał urządzeniu do wej-ścia w stan uśpienia. Jednak i tu z pomocą przy-chodzi jedna z metod Power API – PowerPolicy-Notify(PPNMessage dwMessage, int option). Na czas działania aplikacji dodatkowo należy wy-wołać dla niej tryb pracy UNATTENDED.

Zarządzanie mocą może być również do-konywane na poziomie poszczególnych

Listing 4. Cykliczne uruchamianie aplikacji

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

using System.Reflection;

namespace RunAgainProgram

{

class Program

{

[StructLayout(LayoutKind.Sequential)]

public class SystemTime

{

public ushort wYear;

public ushort wMonth;

public ushort wDayOfWeek;

public ushort wDay;

public ushort wHour;

public ushort wMinute;

public ushort wSecond;

public ushort wMilliseconds;

}

[DllImport("CoreDLL")]

public static extern int CeRunAppAtTime(string application, SystemTime

startTime);

[DllImport("CoreDLL")]

public static extern int FileTimeToSystemTime(ref long lpFileTime,

SystemTime lpSystemTime);

[DllImport("CoreDLL")]

public static extern int FileTimeToLocalFileTime(ref long lpFileTime, ref

long lpLocalFileTime);

public static void RunAppAtTime(string applicationEvent, DateTime

startTime)

{

long fileTimeUTC = startTime.ToFileTime();

long fileTimeLocal = 0;

SystemTime systemStartTime = new SystemTime();

FileTimeToLocalFileTime(ref fileTimeUTC, ref fileTimeLocal);

FileTimeToSystemTime(ref fileTimeLocal, systemStartTime);

CeRunAppAtTime(applicationEvent, systemStartTime);

}

static void Main(string[] args)

{

//wykonaj kod aplikacji

System.Console.WriteLine(DateTime.Now.ToLongTimeString());

//pobierz pełną nazwę pliku EXE ze ścieżką

string exeFile = typeof(Program).Assembly.GetModules()[0].FullyQualifi

edName;

//ustaw następne uruchomienie za godzinę

RunAppAtTime(exeFile, DateTime.Now.AddHours(1.0));

}

}

}

Page 59: SDJ Extra 34 Biblia

58

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Moc pod kontrolą

www.sdjournal.org 59

układów wchodzących w skład urządzenia. Standardowo w rejestrze systemowym w gałęzi HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power\State znaj-duje się lista stanów urządzenia, a dla każde-go z nich zdefiniowana jest lista peryferiów wraz z odpowiadającymi im stanami zasilania. Wartość 0 – odpowiada stanowi D0, 1 – D1 itd. Programista, który chce np. żeby wbudo-wany odbiornik GPS był aktywny przez ca-ły czas działania aplikacji nie musi modyfi-kować jednak zawartości rejestru; wystarczy posłużyć się funkcji Win API – SetPowerRequirement(string pDevice, CEDEVICE_POWER_STATE DeviceState, DevicePowerFlags

DeviceFlags, IntPtr pSystemState, uint StateFlagsZero). Kilka słów na temat para-metrów – oto najważniejsze z nich:

• pDevice jest nazwą modułu sprzętowe-go wchodzącego w skład urządzenia np.

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

podświetlenie to bkl1:, głośniki wav1:, a wbudowany moduł GPS – gpd0:

• DeviceState jest stanem zasilania i przyjmuje wartości od D0 do D4

Pozostałe parametry są zwyczajowo stałe dla wszystkich standardowych wywołań tej funk-cji a rezultatem jej działania w przypadku po-wodzenia niezerowa wartość. Dobrze jest ją za-pamiętać, tak aby na zakończenie programu przywrócić oryginalny stan zasilania dla pery-

feriów. Służy do tego funkcja ReleasePowerRequirement(IntPtr hPowerReq).

Słowem zakończeniaMam nadzieję, że zaprezentowany artykuł wprowadził Czytelników gładko w temat efek-tywnego programowania urządzeń Windows Mobile z uwzględnieniem mechanizmów za-rządzania energią i pozwoli odbiorcom Wa-szych aplikacji na rzadsze używanie ładowarek sieciowych.

Listing 5. Uruchomienie aplikacji w momencie wystąpienia określonego zdarzenia

//mechanizm P/Invoke w celu uzyskania dostępu do CeRunAppAtEvent

[DllImport("coredll.dll", EntryPoint="CeRunAppAtEvent", SetLastError=true)]

private static extern bool CeRunAppAtEvent(string pwszAppName, int lWhichEvent);

//uruchom kalkulator w momencie wyjścia z trybu uśpienia

CeRunAppAtEvent(@"\Windows\Calc.exe", (int)WhichEvent.NOTIFICATION_EVENT_WAKEUP);

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Runtime.InteropServices;

using Microsoft.Win32;

namespace DontStopTheApp

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

//pobierz ustawienia z rejestru

int DelayTime()

{

int retVal = 1000;

RegistryKey key = Registry.LocalMachine.OpenS

ubKey(

@"\SYSTEM\CurrentControlSet\

Control\Power");

object oBatteryTimeout = key.GetValue("BattPow

erOff");

object oACTimeOut = key.GetValue("ExtPowerOf

f");

object oScreenPowerOff = key.GetValue("ScreenP

owerOff");

if (oBatteryTimeout is int)

{

int v = (int)oBatteryTimeout;

if(v>0) retVal = Math.Min(retVal,v);

}

if (oACTimeOut is int)

{

int v = (int)oACTimeOut;

if(v>0) retVal = Math.Min(retVal, v);

}

if (oScreenPowerOff is int)

{

int v = (int)oScreenPowerOff;

if(v>0) retVal = Math.Min(retVal, v);

}

//ustaw czas na 90% z minimalnego czasu

return retVal*(0.9*1000);

}

private void Form1_Load(object sender, EventArgs e)

{

//pobierz najkrótszy czas

int interval = DelayTime();

resetTimer.Interval = interval;

resetTimer.Enabled = true;

}

private void miQuit_Click(object sender, EventArgs

e)

{

this.Close();

}

[DllImport("CoreDLL")]

public static extern int SystemIdleTimerReset();

// resetuj licznik

private void resetTimer_Tick(object sender,

EventArgs e)

{

SystemIdleTimerReset();

}

}

}

Page 60: SDJ Extra 34 Biblia

60

Programowanie Windows Mobile

SDJ Extra 34 Biblia

Listing 7. Wprowadzenie aplikacji w tryb UNATTENDED

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Runtime.InteropServices;

namespace UnAttendedMode

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

[DllImport("CoreDLL")]

public static extern int PowerPolicyNotify(int

dwMessage, int option);

const int PPN_UNATTENDEDMODE = 3;

private void Form1_Load(object sender, EventArgs e) {

//powiadom system o wprowadzeniu dla aplikacji trybu UNATTENDED

PowerPolicyNotify(PPN_UNATTENDEDMODE, -1);

}

private void Form1_Closing(object sender,

CancelEventArgs e)

{

//zwolnij aplikację z trybu UNATTENDED

PowerPolicyNotify(PPN_UNATTENDEDMODE, 0);

}

}

}

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

MARIAN WITKOWSKIJest miłośnikiem oraz entuzjastą wykorzystywania technologii mobilnych w co-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ą 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: [email protected]

Listing 8. Blokada modułu GPS przed wejściem w tryb uśpienia

[Flags()]

public enum DevicePowerFlags

{

None = 0,

POWER_NAME = 0x00000001,

POWER_FORCE = 0x00001000,

POWER_DUMPDW = 0x00002000

}

public enum CEDEVICE_POWER_STATE : int

{

PwrDeviceUnspecified = -1,

D0 = 0,

D1 = 1,

D2 = 2,

D3 = 3,

D4 = 4,

PwrDeviceMaximum

}

[DllImport("CoreDLL")]

public static extern int ReleasePowerRequirement(IntPtr

hPowerReq);

[DllImport("CoreDLL", SetLastError = true)]

public static extern IntPtr SetPowerRequirement

(

string pDevice,

CEDEVICE_POWER_STATE DeviceState,

DevicePowerFlags DeviceFlags,

IntPtr pSystemState,

uint StateFlagsZero

);

IntPtr _gpsPowerRequirements = IntPtr.Zero;

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)

{

if (_gpsPowerRequirements != IntPtr.Zero)

{

ReleasePowerRequirement(_gpsPowerRequirements);

}

}

Page 61: SDJ Extra 34 Biblia
Page 62: SDJ Extra 34 Biblia

62

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 63

Producenci urządzeń mobilnych raz po raz zadziwiają nas techniczny-mi nowinkami stosowanymi w no-

woczesnych telefonach komórkowych. Jed-ną z takich nowinek był niewątpliwie sen-sor ruchu. Urządzenie to, zwane również akcelerometrem bądź przyspieszeniomie-rzem, potrafi mierzyć własny ruch. Mecha-nizm ten, znany i stosowany w praktyce już od dawna (np. do badania ruchu części ma-szyn, przeciążeń samolotów czy chociażby w laptopach w celu wykrywania zmian położe-nia w celu zabezpieczenia dysku twardego), został odkryty na nowo w świecie technolo-gii mobilnych. O zastosowaniach tego urzą-dzenia słyszy się najczęściej w odniesieniu do platformy Apple iPhone i Apple iPod To-uch. Nie każdy jednak wie, że zanim platfor-my te ujrzały światło dzienne, akcelerometr był dostępny na urządzeniach z platformy S60, działających pod kontrolą systemu ope-racyjnego Symbian. W niniejszym artykule przedstawimy, w jaki sposób można opro-gramować sensor ruchu w aplikacjach prze-

znaczonych dla platformy S60, opiszemy również, w jaki sposób przy pomocy akcele-rometru można rozszerzyć funkcjonalność programów działających na pokładzie tele-fonów komórkowych.

S60: przegląd dostępnych API do obsługi akcelerometruPlatformy bazujące na systemie operacyj-nym Symbian cieszą się opinią rozwiązań trudnych do oprogramowania. Niestety, w odniesieniu do obsługi akcelerometru wię-cej w tym stwierdzeniu prawdy niż przesa-dy. Otóż już na samym początku programi-sta pretendujący do napisania aplikacji obsłu-gującej sensor ruchu staje przed trudnym wy-borem: które z dostępnych API zastosować? Kłopot w tym, że dostęp do danych z akce-lerometru można na chwilę obecną uzyskać przez trzy różne API języka C++ oraz jedno API języka Python. Poniżej kilka słów na te-mat każdego z nich:

• Sensor Plugin: to API zostało udostępnio-ne jako pierwsze; współpracuje ono z na-stępującymi urządzeniami: Nokia 5500, Nokia N82, Nokia N93i, Nokia N95 oraz Nokia N95 8GB.

• Sensor API: ma to samo pokrycie urzą-dzeń co Sensor Plugin, jednak na czę-

ści z nich dostępna jest mniejsza ilość funkcji. Ponadto to API nie jest częścią platformy S60 i nie jest dostępne na urządzeniach innego producenta niż Nokia.

• S60 Sensor Framework: dostępny na urzą-dzeniach S60 5th ed, S60 3rd ed FP2 i Nokia E66

W dalszej części artykułu przedstawimy sposób odczytu danych z akcelerometru przy użyciu pierwszego i drugiego API z przedstawionej powyżej listy. Testowa apli-kacja stworzona na potrzeby niniejszego ar-tykułu działa na urządzeniach z serii S60 3rd Edition. Została ona skonstruowana przy pomocy SDK S60 3rd edition FP 2 roz-szerzonego o następujące wtyczki:

• Sensor plug-in (http://www.forum.nokia.com/info/sw.nokia.com/id/4284ae69-d37a-4319-bdf0-d4acdab39700/Sensor_plugin_S60_3rd_ed.exe.html);

• Sensor API Plug-in (ht tp : / /w w w.fo r um .n oki a .c o m /info/

sw.nokia.com/id/8059e8ae-8c22-4684-be-6b-d40d443d7efc/Sensor_ API_Plug_in_S60_3rd_FP2.html).

Instalacja wtyczek do obsługi akcelerometruSkoro już zdecydowaliśmy, które z dostęp-nych wtyczek użyjemy do eksperymentów z akcelerometrem, warto wspomnieć kil-ka słów na temat ich instalacji. Pisząc ni-niejszy artykuł, założyliśmy, iż czytające go osoby posiadają przynajmniej podsta-wowe doświadczenia z programowaniem

Obsługa akcelerometru na platformie Series60

Akcelerometr, bądź inaczej – sensor ruchu, od stosunkowo niedawna jest wykorzystywany jako rozszerzenie technologicznych możliwości nowoczesnych telefonów komórkowych. Niniejszy artykuł pokazuje, jak oprogramować akcelerometr w aplikacjach Symbian OS, działających na platformie S60, oraz jak w praktyce wykorzystać potencjał tego urządzenia.

Dowiesz się:• W jaki sposób oprogramować akcelerometr

w aplikacjach Symbian OS na platformę S60;• Jak wykorzystać akcelerometr w praktyce

przy tworzeniu własnych aplikacji.

Powinieneś wiedzieć:• Podstawy programowania aplikacji Symbian

OS w języku C++;• Podstawy teorii wzorców projektowych, a w

szczególności wzorca Obserwator.

Poziom trudności

Wykorzystaj w pełni możliwości telefonu komórkowego!

Page 63: SDJ Extra 34 Biblia

62

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 63

aplikacji dla platformy Symbian S60. Z te-go też względu postanowiliśmy nie opisy-wać procesu instalacji samego SDK, a jedy-nie dodatkowych wtyczek. Na samym po-czątku warto zauważyć, iż warto zainstalo-wać obydwa plug-iny do tego samego SDK; dzięki temu budowanie przykładowej apli-kacji obędzie się bez przełączania aktywne-go SDK.

Rozszerzenie Sensor Plug-in dostarczane jest w postaci pojedynczej paczki instalacyj-nej (Sensor_plugin_S60_3rd_ed.exe). Po jej uruchomieniu uruchamia się instalator, któ-ry krok po kroku przeprowadza nas przez ca-ł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-inW 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 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

tej pory dla osi X będą wartościami odpo-wiadającymi osi Y i na odwrót. Dodatko-wo należy pamiętać o tym, iż otrzymywane wartości są ze znakiem.

Aby przeciążona metoda HandleDataEventL() była wywołana, należy dodać obiekt imple-mentującej ją klasy jako obserwatora do obiek-tu typu CRRSensorApi przy pomocy metody AddDataListener.

W celu utworzenia instancji klasy CRRSensorApi należy najpierw pobrać li-stę istniejących sensorów za pomocą sta-tycznej metody FindSensorsL z tej sa-mej klasy. Jako parametr podajemy tablicę

RArray<TRRSensorInfo>, która – jak ławo się domyśleć – po wywołaniu będzie wy-pełniona obiektami typu TRRSensorInfo, zaś te zawierać będą informację o dostęp-nych sensorach. W naszym przypadku oka-zało się, iż telefon Nokia N95 posiada wię-cej niż jeden sensor. W aparacie tym są do-stępne dwa sensory: RotSensor (ang. rotate sensor) oraz AccSensor (ang. acceleration sen-sor). W przypadku sensora rotacji dane są przekazywane tylko w polu iSensorData1 i oznaczają orientację telefonu (0o, 90o, 180o i 270o). Nas interesuje oczywiście drugi z dostępnych sensorów, który zapi-

Rysunek 1. Instalacja rozszerzenia Sensor Plug-in: krok pierwszy

Rysunek 2. Instalacja rozszerzenia Sensor Plug-in: krok drugi

Listing 1. Definicja interfejsu MRRSensorDataListener

class MRRSensorDataListener

{

public:

virtual void HandleDataEventL(

TRRSensorInfo aSensor,

TRRSensorEvent aEvent )

= 0;

};

Page 64: SDJ Extra 34 Biblia

64

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 65

suje we wszystkich trzech polach dane na temat ruchu (w postaci przyśpieszenia) na osiach X, Y oraz Z.

Korzystając z rozszerzenia Sensor Plug-in, należy pamiętać o tym, aby do listy dołą-czanych bibliotek w pliku definicji projektu (mmp) dodać wpis rrsensorapi.lib. Warto też zauważyć, że biblioteka ta jest niedostępna dla emulatora.

Sensor API Plug-inZrąb (ang. framework) oferowany w ramach rozszerzenia Sensor API Plug-in składa się z trzech elementów:

• Sensor Plug-in API;• Channel Finder API;• Sensor Channel API.

Pierwszy z wymienionych elementów nie jest załączony w ogólnodostępnym SDK, ja-ko że jest on przeznaczony tylko dla licen-cjobiorców platformy S60. Drugie API słu-ży do pobierania listy dostępnych czujni-ków. Przy pomocy ostatniego API możemy odczytywać dane z czujnika, opakowane w deskryptor. Warto zauważyć, że Sensor Channel API nie zamienia danych osi X i osi Y w przypadku obrócenia ekranu; nale-

ży o tym pamiętać, gdy chcemy wykorzy-stać te dane np. do sterowania grą.

Rozważmy teraz bardziej szczegóło-wo, w jaki sposób działają wymienione API. Zaczniemy od pobrania listy czujni-ków. W tym celu należy utworzyć tablicę RArray dla przechowywania obiektów typu TSensrvChannelInfo; definicja typu takiej ta-blicy zdefiniowana jest w pliku sensrvtypes.h ja-ko RSensrvChannelInfoList. Następnie two-rzymy instancję klasy CSensrvChannelFinder (zdefiniowanej w pliku sensrvchannelfin-der.h). Poza tablicą, którą wypełni metoda 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 KSensrvChannelTypeIdAccelerometerXYZAxisData 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-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

Rysunek 3. Instalacja rozszerzenia Sensor Plug-in: krok trzeci

Rysunek 4. Instalacja rozszerzenia Sensor Plug-in: krok czwarty

Listing 2. Definicje klas TRRSensorInfo oraz TRRSensorEvent

class TRRSensorInfo

{

public:

TInt iSensorCategory;

TInt iSensorId;

TBuf<KMaxSensorName> iSensorName;

};

class TRRSensorEvent

{

public:

TInt iSensorData1;

TInt iSensorData2;

TInt iSensorData3;

};

Page 65: SDJ Extra 34 Biblia

64

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 65

opóźnienie maksymalne dla sensora po-między kolejnymi uaktualnieniami obiek-tu MSensrvDataListener. W moim przykła-dzie wartości, jakich użyłem, to kolejno 1, 1, 0; nie jest to dobre rozwiązanie ze względów wydajnościowych, ale na demonstrowany przykład wystarczające.

Po wykonaniu opisanych wyżej czynności, dane z akcelerometru będą spływać do dziedzi-czącego po interfejsie MSensrvDataListener obiektu obserwatora poprzez wołanie meto-dy zwrotnej ChannelChangeDetected(). Na-główek tej metody przedstawiony jest na Li-stingu 4.

Opis wyłuskania danych o przyśpieszeniu z przekazywanego do tej metody obiektu ty-pu TSensrvChannelInfo przedstawiony jest w dalszej części artykułu.

Wrapper na obydwa APIPo przeczytaniu ostatnich dwóch punk-tów niniejszego artykułu można odnieść wrażenie, iż obsługa koncepcyjnie proste-go urządzenia, jakim jest akcelerometr pod system Symbian OS, jest – mówiąc deli-

katnie – nieco przekombinowana. Krót-kie podsumowanie mówi samo za siebie: dwa równoległe API dość mocno różnią-ce się od siebie, przy czym drugie z nich (Sensor API Plug-in) stanowi mocno roz-budowany zrąb do odczytywania danych z sensorów. Z punktu widzenia programi-sty, który chciałby po prostu pobierać war-tości przyśpieszenia na osiach X, Y i Z, nie martwiąc się na poziomie kodu aplikacji o to, z jakim ma do czynienia urządzeniem, istniejący stan rzeczy wydaje się być moc-no zniechęcający do dalszych eksperymen-tów. Mając takie same odczucia, postano-wiliśmy zaproponować Czytelnikom ni-niejszego artykułu rozwiązanie tej mało dogodnej sytuacji w postaci wygodnego opakowania (ang. wrapper) na obydwa API. Nasz wrapper skupia się tylko i wyłącznie na sensorze odczytującym wartości przy-śpieszenia na osiach X, Y, Z, oferuje prosty interfejs, ukrywając wszelkie szczegóły im-plementacyjne przed korzystającym z nie-go programistą, i, co istotne – na poziomie interfejsu jest całkowicie niezależny od wy-

korzystywanego rozszerzenia (Sensor Plug-in bądź Sensor API Plug-in).

Koncepcyjnie schemat korzystania z naszego wrappera jest podobny do opisa-nych wyżej rozwiązań: najpierw należy po-brać listę dostępnych czujników, następ-nie stworzyć czujnik i przekazać do nie-go obiekt nasłuchujący. W kolejnych pod-punktach opiszemy implementację nasze-go opakowania oraz przykład jego wyko-rzystania przedstawiony w kontekście pro-stej aplikacji testowej.

Implementacja wrapperaW niniejszym punkcie opiszemy implemen-tację opakowania przykrywającego dwa API do obsługi akcelerometru: Sensor Plug-in oraz Sensor API Plug-in. Nasz wrapper zaimplemen-towany jest w postaci klasy CAccelerometer. Na początek rozważmy interfejs tejże klasy przedstawiony na Listingu 5.

Listing ten przedstawia zawartość pli-ku nagłówkowego Accelerometer.hpp, któ-rego sporą część zajmuje definicja interfej-su interesującej nas klasy. Patrząc na pierw-sze linijki deklaracji klasy, można zauwa-żyć, że dziedziczy ona z CBase, czyli jej in-stancje będą tworzone dynamicznie (wy-nika to z programistycznych konwencji określonych przez Symbiana; zakładamy, iż Czytelnik niniejszego tekstu zna wspo-mniane konwencje). W dalszej kolejności klasa dziedziczy po interfejsie jednego z obserwatorów: MRRSensorDataListener bądź MSensrvDataListener. Konkret-ny interfejs dobierany jest w zależno-ści od dostępności definicji symboli 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

��

��

��

��

��

��

Listing 3. Nagłówek metody CSensrvChannel::StartDataListeningL

virtual void StartDataListeningL(

MSensrvDataListener* aDataListener,

const TInt aDesiredCount,

const TInt aMaximumCount,

const TInt aBufferingPeriod ) = 0;

Listing 4. Nagłówek metody MSensrvChannelListener::ChannelChangeDetected

virtual void ChannelChangeDetected(

const TSensrvChannelInfo& aDetectedChannel,

TSensrvChannelChangeType aChangeType ) = 0;

Page 66: SDJ Extra 34 Biblia

66

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 67

specyficzne dla poszczególnych API syste-mowe pliki nagłówkowe.

W tym momencie jasne staje się to, w ja-ki sposób aplikacje mogą wykorzystywać nasz wrapper. W tym celu trzeba będzie skorzystać z publicznego API klasy CAccelerometer i zdefi-niować globalnie jeden z opisanych wyżej sym-boli preprocesora (można to zrobić z poziomu pliku mmp). Zmiana tej definicji będzie powo-dować automatyczne przełączanie się pomię-dzy obydwoma API do obsługi akcelerome-tru. Po jej wprowadzeniu wystarczy ponownie skompilować projekt w celu uzyskania paczki instalacyjnej wspierającej zadane API.

Wróćmy do interfejsu klasy CAccelerometer. Na początku mamy tam dwie publiczne meto-dy statyczne. Pierwsza z nich (CAccelero-meter::NewL()) jest częścią symbianowe-go idiomu dwufazowej konstrukcji obiektów i nie będziemy jej w tym miejscu szczegóło-wo opisywać. Druga metoda (CAccelerome-ter::GetAvailableSensorsL()) jest, z nasze-go punktu widzenia, bardziej interesująca: bę-dziemy wykorzystywać ją w celu pobierania in-formacji o dostępnych sensorach ruchu. Meto-da ta jako parametr przyjmuje wskaźnik na ta-blicę deskryptorów (czyli symbianowych na-pisów), która zostanie wypełniona odpowied-nimi informacjami. Warto w tym miejscu za-uważyć, iż w publicznym interfejsie klasy CAccelerometer nie stosujemy żadnych dedy-kowanych struktur do opisu sensora (np. takich jak opisywana wcześniej klasa TRRSensorInfo). Chcąc jak najbardziej uprościć ten interfejs, przechowujemy informację na temat sensora ruchu w postaci zwykłego ciągu znaków.

Kolejne dwie, niestatyczne metody: AddObserverL oraz RemoveObserver, słu-żą do zarządzania obiektami obserwatorów stanu akcelerometru. W naszym wrapperze zastosowaliśmy bardzo minimalistyczny in-terfejs obserwatora; interfejs ten dostępny w postaci klasy MAccelerometerObserver jest zdefiniowany na Listingu 5. Kluczowa jest przede wszystkim jego metoda zwrotna:

virtual void OnAccEventL( TInt aAxisX,

TInt aAxisY, TInt

aAxisZ ) = 0;

Przyjmuje ona po prostu trzy wartości typu TInt reprezentujące przyśpieszenie na po-szczególnych osiach.

Tak wygląda publiczna część interfejsu klasy CAccelerometer. W celu pełnego zro-zumienia mechanizmów działania nasze-go opakowania przyjrzyjmy się teraz prywat-nym składowym oraz metodom tej klasy. Pry-watny interfejs klasy dzieli się na dwie czę-ści: pierwsza z nich jest niezależna od wyko-rzystywanego API, druga – zmienia się w za-leżności od tego, które rozszerzenie stosuje-my. Rozważmy na początek część niezależną. Mamy tutaj prywatny konstruktor oraz me-

Listing 5. Interfejs klasy CAccelerometer

#if !defined __INCLUDED_ACCELEROMETER_WRAPPER_HPP__

#define __INCLUDED_ACCELEROMETER_WRAPPER_HPP__

#include <badesca.h>

#include "AccConfig.hpp"

namespace Acc

{

class MAccelerometerObserver

{

public:

virtual void OnAccEventL( TInt aAxisX, TInt aAxisY, TInt aAxisZ ) = 0;

};

class CAccelerometer :

public CBase

#if defined (ACC_SENSOR_API)

, public MRRSensorDataListener

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

, public MSensrvDataListener

#endif

{

public:

static CAccelerometer* NewL( const TDesC& aSensor );

static void GetAvailableSensorsL( CDesCArray* aArray );

void AddObserverL( MAccelerometerObserver* aObserver );

void RemoveObserver( MAccelerometerObserver* aObserver );

~CAccelerometer();

private:

CAccelerometer();

void ConstructL( const TDesC& aSensor );

RPointerArray<MAccelerometerObserver> iObservers;

// API specific

#if defined (ACC_SENSOR_API)

static void GetSensorsInfoL( RArray<TRRSensorInfo>& aInfoArray );

static inline TInt FindSensorIndex( const RArray<TRRSensorInfo>& aInfoArray,

const TDesC& aSensorName );

void HandleDataEventL( TRRSensorInfo aSensor, TRRSensorEvent aEvent );

CRRSensorApi* iSensor;

#elif defined(ACC_S60_SENSOR_FRAMEWORK)

static inline TInt FindSensorIndex( const RSensrvChannelInfoList& aInfoArray,

const TDesC& aSensorName );

void DataError( CSensrvChannel& aChannel, TSensrvErrorSeverity aError );

void DataReceived( CSensrvChannel& aChannel, TInt aCount, TInt aDataLost );

void GetDataListenerInterfaceL( TUid aInterfaceUid, TAny*& aInterface );

CSensrvChannel* iSensor;

#endif

};

};

#include "Accelerometer.inl"

#endif // __INCLUDED_ACCELEROMETER_WRAPPER_HPP__

Listing 6. Zawartość pliku AccConfig.hpp

#if !defined __INCLUDED_ACC_CONFIG_HPP__

#define __INCLUDED_ACC_CONFIG_HPP__

#if defined (ACC_SENSOR_API)

# include <rrsensorapi.h>

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

# include <sensrvchannelfinder.h>

# include <sensrvchannel.h>

# include <sensrvaccelerometersensor.h>

# include <sensrvdatalistener.h>

#endif

#endif // __INCLUDED_ACC_CONFIG_HPP__

Page 67: SDJ Extra 34 Biblia

66

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 67

todę ConstructL(), które stanowią szczegół implementacyjny (są częścią wspomniane-go wcześniej mechanizmu dwufazowej kon-strukcji obiektów stosowanej przy progra-mowaniu aplikacji C++ pod Symbain OS), i nie będziemy ich szczegółowo opisywać. Ko-lejnym i zarazem ostatnim elementem w tej części API jest prywatna składowa:

RPointerArray<MAccelerometerObserver>

iObservers;

Składowa ta reprezentuje dynamiczną listę przechowującą wskaźniki do obserwatorów akcelerometru.

Druga część prywatnego interfejsu kla-sy jest zależna od wykorzystywanego API; z tego też względu jest ona umieszczona w dwóch sekcjach kontrolowanych przez instrukcje sterujące preprocesora: #if

defined / #elif defined / #endif. W każ-dej z tych sekcji umieszczone są specyficz-ne dla danego API deklaracje metod oraz pól potrzebnych do inicjacji akcelerometru oraz przechwytywania generowanych przez niego zdarzeń.

Podsumowując opis interfejsu klasy re-prezentującej sensor ruchu, warto nadmie-nić, iż została ona umieszczona w przestrze-ni nazw Acc.

Przejdźmy teraz do opisu implementacji klasy CAccelerometer. W pliku Accelerome-ter.inl (patrz: Listing 7) umieściliśmy stałe oraz metody typu inline.

Warto zwrócić uwagę, że stała KObserversArrayGranularity została ukryta w nie-nazwanej przestrzeni nazw, co gwarantu-je ograniczenie jej widoczności wyłącznie w zakresie pliku, w którym ją zdefiniowa-no. Oprócz definicji konstruktora plik Ac-celerometer.inl zawiera jeszcze dwie im-plementacje metod CAccelerometer::

FindSensorIndex. Metoda ta odpowiada za wyszukanie indeksu sensora w zadanej li-ście na podstawie jego nazwy. W zależności od wykorzystywanego rozszerzenia przyj-

muje ona jako pierwszy parametr obiekt ty-pu const RArray<TRRSensorInfo>& bądź const RSensrvChannelInfoList&.

Główna część implementacji wrappera ukryta jest w pliku źródłowym Accelerome-ter.cpp. Na początek przyjrzymy się definicjom publicznych metod klasy CAccelerometer. Na pierwszy ogień pójdzie statyczna metoda

CAccelerometer::GetAvailableSensorsL() (patrz: Listing 8).

Metoda ta odpowiada za wypełnienie prze-kazanej tablicy napisów (aArray) nazwami dostępnych sensorów. W przypadku korzy-stania z Sensor API (ACC_SENSOR_API), imple-mentacja tej metody jest trywialna i polega na wywołaniu CRRSensorApi::FindSensorsL()

Listing 7. Zawartość pliku Accelerometer.inl

namespace Acc

{

namespace

{

const TInt KObserversArrayGranularity = 2;

}

inline CAccelerometer::CAccelerometer()

: iObservers( KObserversArrayGranularity )

{

}

#if defined (ACC_SENSOR_API)

inline TInt Caccelerometer::FindSensorIndex(

const RArray<TRRSensorInfo>& aInfoArray,

const TDesC& aSensorName )

{

for ( TInt i = 0; i < aInfoArray.Count(); ++i )

{

if ( aInfoArray[i].iSensorName == aSensorName )

{

return i;

}

}

return KErrNotFound;

}

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

inline TInt Caccelerometer::FindSensorIndex(

const RSensrvChannelInfoList& aInfoArray,

const TDesC& aSensorName )

{

for ( TInt i = 0; i < aInfoArray.Count(); ++i )

{

HBufC* str = HBufC::NewLC( aInfoArray[i].iLocation.Length() );

str->Des().Copy( aInfoArray[i].iLocation );

if ( *str == aSensorName )

{

CleanupStack::PopAndDestroy( str );

return i;

}

CleanupStack::PopAndDestroy( str );

}

return KErrNotFound;

}

#endif

} // namespace Acc

Rysunek 7. Zrzut ekranu z testowej aplikacji Graph

Page 68: SDJ Extra 34 Biblia

68

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 69

oraz skopiowaniu uzyskanych za jej pośred-nictwem danych do obiektu aArray. W przy-padku korzystania z alternatywnego API (ACC_S60_SENSOR_FRAMEWORK), sprawa jest nieco bardziej skomplikowana. Musimy po kolei: stworzyć obiekt poszukiwacza (ang. finder), stworzyć i odpowiednio zainicjować obiekt filtra (TsensrvChannelInfo), i korzy-stając z tych dwóch, wykonać zapytanie:

finder->FindChannelsL( channelInfoList,

searchConditions );

Po wykonaniu powyższych kroków możemy skopiować z channelInfoList nazwy do-stępnych sensorów. Odpowiada za to dedy-kowana pętla for.

Kolejny istotny fragment implementacji na-szego wrappera to kod inicjujący umieszczony w metodzie konstruującej ConstructL(). Za-wartość tej metody przedstawiona jest na Li-

stingu 9. Metoda ta jako parametr otrzymuje nazwę sensora (const TDesC& aSensor), któ-ry ma być obsługiwany. Jej zwartość jest w du-żej mierze tożsama z implementacją opisanej wcześniej metody GetAvailableSensorsL, ty-le że zamiast zapisywania nazw dostępnych sen-sorów na listę, wyszukiwany jest konkretny ak-celerometr (określony przez nazwę zapisaną w aSensor), tworzony jest obiekt go reprezen-tujący (CRRSensorApi bądź CSensrvChannel, w zależności od stosowanego rozszerzenia), po czym instancja nowo tworzonej klasy CAccelerometer podłącza się do tego obiektu jako obserwator. Odpowiadają za to wywołania:

iSensor->AddDataListener( this );

oraz:

iSensor->StartDataListeningL( this, 1, 1,

0 );

W ten sposób, zaraz po pomyślnej konstruk-cji, nasz wrapper jest nieustannie notyfiko-wany o zdarzeniach generowanych przez sensor ruchu. W przypadku niepowodzenia, tj. wtedy, gdy żądane urządzenie nie jest do-stępne, funkcja konstruująca rzuca symbia-nowy wyjątek (User::Leave) z kodem błędu KErrNotFound.

W odniesieniu do implementacji publicz-nego interfejsu klasy CAccelerometer, pozo-stały nam do rozważania tylko metody obsłu-gujące obserwatorów oraz destruktor. Imple-mentacje tych metod przedstawione są na Li-stingu 10.

Zarządzanie obserwatorami sprowadza się do dwóch operacji: dodanie (CAcce-lerometer::AddObserverL()) i usunięcie (Caccelerometer::RemoveObserver()). Implementacje tych metod są trywial-ne i polegają przede wszystkim na funk-cjonalności kontenera RPointerArray<M

AccelerometerObserver>. Kontener ten (zaimplementowany jako szablon kla-sy) przechowuje w przypadku nasze-go wrappera wskaźniki do obiektów ty-pu MAccelerometerObserver. W przypad-ku operacji dodawania, sprawdzamy naj-pierw, czy obiekt obserwatora nie był już wcześniej dodany (warto tu zauważyć, iż to przeszukiwanie odbywa się po adresie wskaźnika obserwatora, a nie na bazie po-równywania zawartości pól obiektu), a je-śli ten warunek nie jest spełniony, to obser-wator jest dodany do tablicy. W przypadku usuwania, sprawa jest prosta: korzystając z metody RPointerArray<MAccelerometerObserver>::Remove(), usuwamy zadanego obserwatora. W destruktorze klasy nisz-czymy tablicę obserwatorów (w tym miej-scu trzeba podkreślić, iż niszczona jest tyl-ko tablica, obiekty obserwatorów pozosta-

Listing 8. Definicja metody CAccelerometer::GetAvailableSensorsL()

namespace Acc

{

void CAccelerometer::GetAvailableSensorsL( CDesCArray* aArray )

{

#if defined (ACC_SENSOR_API)

const TInt KSensorGranularity = 2;

RArray<TRRSensorInfo> array( KSensorArrayGranularity );

CRRSensorApi::FindSensorsL( array );

for ( TInt i = 0; i < array.Count(); ++i )

{

aArray->AppendL( array[ i ].iSensorName );

}

array.Close();

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

CSensrvChannelFinder* finder = CSensrvChannelFinder::NewLC();

RSensrvChannelInfoList channelInfoList;

CleanupClosePushL( channelInfoList );

TSensrvChannelInfo searchConditions;

searchConditions.iChannelType = KSensrvChannelTypeIdAccelerometerXYZAxisData;

finder->FindChannelsL( channelInfoList, searchConditions );

for( TInt i = 0; i < channelInfoList.Count(); ++i )

{

HBufC* str = HBufC::NewLC( channelInfoList[i].iLocation.Length() );

str->Des().Copy( channelInfoList[i].iLocation );

aArray->AppendL( *str );

CleanupStack::PopAndDestroy( str );

}

channelInfoList.Close();

CleanupStack::Pop( &channelInfoList );

CleanupStack::PopAndDestroy( finder );

#endif

}

}

Rysunek 8. Zrzut ekranu z gry Labyrinth

Page 69: SDJ Extra 34 Biblia

68

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 69

ją nienaruszone) oraz obiekt reprezentują-cy sensor.

Ostatnią część naszego opakowania stano-wi implementacja prywatnych metod, zależ-nych od rozszerzenia. Są to funkcje zwrotne wymagane przez poszczególne API; za pomo-cą tychże funkcji wrapper odbiera informacje o zdarzeniach generowanych przez sensor ru-chu. Implementacja tych metod przedstawio-na jest na Listingu 11.

W przypadku korzystania z rozszerzenia Sensor Plug-in, sprawa jest stosunkowo prosta, jako że mamy do obsłużenia tylko jedną me-todę: CAccelerometer::HandleDataEventL. Jednym z parametrów przekazywanych do tej metody jest obiekt opisywanej wcze-śniej klasy TRRSensorEvent. W tej sytu-acji, w ciele metody CAccelerometer::

HandleDataEventL wystarczy wyłuskać ze wspomnianego obiektu odpowiednie skła-dowe (iSensorData1, iSensorData2 oraz iSensorData3) i przekazać je, gdzie trzeba, iterując po kolejnych elementach tablicy z obserwatorami.

Z kolei kiedy wykorzystujemy alterna-tywne rozszerzenie, musimy zaimplemen-tować aż trzy metody zwrotne:

• CAccelerometer::DataError(), • CAccelerometer::DataReceived() • oraz CAccelerometer::GetDataListene

rInterfaceL().

Na szczęście, biorąc pod uwagę założenia co do funkcjonalności naszego opakowania, pierwsza i trzecia z wymienionych metod nie wymagają dostarczenia implementacji. Z tego też względu ich ciała pozostają puste. Główny kod obsługujący zdarzenia genero-

wane przez sensor ruchu znajduje się w ciele metody CAccelerometer::DataReceived(). Idea działania tej metody jest bardzo po-dobna do jej odpowiednika z Sensor API: wyłuskać dane i przekazać je do obserwato-rów. Jednakże, o ile w przypadku metody CAccelerometer::HandleDataEventL() by-

ło to zadanie trywialne, o tyle w przypadku CAccelerometer::DataReceived() sprawa jest nieco bardziej zagmatwana. Trick polega na tym, iż interesujące nas informacje są za-pakowane w deskryptor, czyli innymi słowy w bufor, i musimy je sobie sami wypakować. Służy do tego struktura typu TPckgBuf<TSe

Listing 9. Definicja metody CAccelerometer::ConstructL()

namespace Acc

{

void CAccelerometer::ConstructL( const TDesC& aSensor )

{

#if defined (ACC_SENSOR_API)

RArray<TRRSensorInfo> array( KSensorArrayGranularity );

CleanupClosePushL( array );

CRRSensorApi::FindSensorsL( array );

TInt sensorIndex = FindSensorIndex( array, aSensor );

if ( sensorIndex < 0 )

{

CleanupStack::PopAndDestroy();

User::Leave( KErrNotFound );

}

iSensor = CRRSensorApi::NewL( array[ sensorIndex ] );

iSensor->AddDataListener( this );

CleanupStack::PopAndDestroy();

return;

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

CSensrvChannelFinder* finder = CSensrvChannelFinder::NewLC();

RSensrvChannelInfoList channelInfoList;

CleanupClosePushL( channelInfoList );

TSensrvChannelInfo searchConditions;

searchConditions.iChannelType = KSensrvChannelTypeIdAccelerometerXYZAxisD

ata;

finder->FindChannelsL( channelInfoList, searchConditions );

TInt sensorIndex = FindSensorIndex( channelInfoList, aSensor );

if ( sensorIndex < 0 )

{

channelInfoList.Close();

CleanupStack::Pop( &channelInfoList );

CleanupStack::PopAndDestroy( finder );

User::Leave( KErrNotFound );

}

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

iSensor->OpenChannelL();

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

channelInfoList.Close();

CleanupStack::Pop( &channelInfoList );

CleanupStack::PopAndDestroy( finder );

return;

#endif

}

}Rysunek 9. Zrzut ekranu z aplikacji iMilk: szklanka trzymana prosto

Page 70: SDJ Extra 34 Biblia

70

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 71

nsrvAccelerometerAxisData>. Po przeka-zaniu tej struktury do metody GetData() w obiekcie reprezentującym kanał (CSen-srvChannel& aChannel) możemy wycią-gnąć z niej interesujące nas dane:

TSensrvAccelerometerAxisData data = pckg();

Warto też zauważyć, że zanim zaczniemy przetwarzać zdarzenie, warto sprawdzić, czy mamy do czynienia z interesującym nas typem kanału. W tym celu ciało metody opakowane jest następującą instrukcją wa-runkową:

if ( aChannel.GetChannelInfo().iChannelT

ype ==

KSensrvChannelTypeIdAccelero

meterXYZAxisData )

{ /* ... */ }

Na tym kończymy opis implementacji kla-sy CAccelerometer. Zainteresowanych Czytelników zapraszamy do analizy ko-du źródłowego naszego wrappera, któ-ry jest umieszczony na płycie DVD do-łączonej do niniejszego czasopisma. Opi-sane wyżej pliki (AccConfig.hpp, Accelero-meter.cpp, Accelerometer.hpp oraz Accelero-meter.inl) znajdują się w podkatalogu src/accelerometer.

W dalszej części artykułu pokażemy, jak zastosować zaprezentowane opakowania przy tworzeniu aplikacji korzystającej z sen-sora ruchu.

Wrapper w praktyceW poprzednim punkcie przedstawiliśmy szczegółowy opis implementacji wrappera opakowującego dwa API do obsługi sen-sora ruchu. Teraz, na przykładzie testowej aplikacji, pokażemy, jak można wykorzy-stać go w praktyce.

Kod źródłowy wspomnianej aplikacji znajduje się w podkatalogu src/graph, w ar-chiwum z materiałami dołączonymi do ni-niejszego artykułu. Zakładamy, iż Czytelnik niniejszego tekstu zna podstawy programo-wania aplikacji pod Symbian OS przy użyciu języka C++. Z tego też względu nie będzie-my w tym miejscu opisywać całego szkiele-tu aplikacji, skupimy się za to na elementach bezpośrednio wykorzystujących nasz wrap-per. Aplikacja testowa posiada jeden widok, w którym rysowane są odczyty każdej z osi. Widok jest odświeżany po każdym odczycie. Odczyty z każdej osi są skalowane do wyso-kości ekranu, w zależności od dotychcza-sowych ekstremalnych wartości odczytów. Za inicjację oraz obsługę sensora ruchu od-powiada klasa CGraphView, reprezentująca wspomniany widok.

Na początek zbadajmy jej interfejs (patrz: Listing 12).

Pierwsze miejsce, na które należy zwrócić uwagę, to początek definicji klasy. Widać tu pierwszy ślad zastosowania naszego wrappe-ra. Mowa tu oczywiście o publicznym dzie-dziczeniu klasy CGraphView po interfejsie Acc::MAccelerometerObserver. Następ-stwem tego faktu jest pojawienie się nastę-pującej deklaracji w publicznym interfej-sie klasy:

void OnAccEventL( TInt aAxisX, TInt

aAxisY, TInt aAxisZ

);

Za pomocą tej metody zwrotnej, widok aplikacji będzie odbierał informacje od sensora. Informacje te będą opakowywane w strukturę TReading, a następnie prze-chowywane w tablicy, a na ich podsta-wie rysowany będzie wykres (ang. graph): stąd właśnie się wzięła taka, a nie inna na-zwa naszej testowej aplikacji. W prywat-nej części interfejsu da się zauważyć skła-dową: Acc::CAccelerometer* iSensor, która reprezentuje akcelerometr. Warto też zwrócić uwagę na prywatną metodę CreateSensorL() oraz składowe iArray, iMax oraz iMin. Przeznaczenie tych skła-dowych opisane będzie w dalszej części artykułu.

Na Listingu 13 przedstawiona jest zawar-tość pliku GraphAppView.cpp, z pominię-ciem definicji metod CGraphView::Draw() oraz CGraphView::DisplayPopupListLC(), które opisane będą nieco później.

W pierwszej kolejności przyjrzymy się implementacji metody CGraphView:

Listing 10. Implementacja metod do obsługi obserwatorów oraz destruktora w klasie CAccelerometer

namespace Acc

{

void CAccelerometer::AddObserverL( MAccelerometerObserver* aObserver )

{

if ( KErrNotFound == iObservers.Find( aObserver ) )

{

iObservers.AppendL( aObserver );

}

}

void CAccelerometer::RemoveObserver( MAccelerometerObserver* aObserver )

{

TInt pos = iObservers.Find( aObserver );

if ( pos >= 0 )

{

iObservers.Remove( pos );

}

}

CAccelerometer::~CAccelerometer()

{

iObservers.Close();

#if defined (ACC_SENSOR_API)

delete iSensor;

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

delete iSensor;

#endif

}

} Rysunek 10. Zrzut ekranu z aplikacji iMilk: szklanka po przechyleniu

Page 71: SDJ Extra 34 Biblia

70

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 71

:CreateSensorL(). Metoda ta wywo-ływana jest z poziomu CGraphView::

ConstructL(), czyli w drugiej fazie kon-struowania obiektu reprezentującego wi-dok. Jej zadaniem jest stworzenie i kon-figuracja obiektu reprezentującego sensor ruchu. W tym celu tworzona jest na po-czątku dynamiczna tablica do przecho-wywania nazw dostępnych sensorów. Ta-blica ta wypełniana jest danymi w trakcie wywołania:

Acc::CAccelerometer::GetAvailableSensorsL(

array );

W kolejnym kroku użytkownik jest proszo-ny o wybór sensora z dostępnej listy (służy po temu funkcja DisplayPopupListLC(), która opisana będzie kilka akapitów da-lej). Po wybraniu docelowego sensora, wi-dok tworzy obiekt wrappera i podłącza się do niego jako obserwator. Od tego momen-tu metoda CGraphView::OnAccEventL() zaczyna odbierać zdarzenia generowane przez wybrany akcelerometr. W przypad-ku naszego widoku, informacje napływają-ce z sensora są pakowane w strukturę i do-łączane do listy iArray.

Oprócz tego przy okazji każdego wystą-pienia takiego zdarzenia zapamiętywane są maksymalne wielkości wychyleń na wszyst-kich osiach; będą one wykorzystywane do skalowania wykresu rysowanego w funk-cji CGraphView::Draw(). Ostatnia linia w tej funkcji zawiera wywołanie DrawNow();, które wymusza odrysowanie powierzchni widoku.

Wróćmy jeszcze na moment do funkcji CGraphView::DisplayPopupListLC(). De-finicja tej funkcji przedstawiona jest na Li-stingu 14.

Funkcja ta buduje proste menu kontek-stowe zawierające nazwy poszczególnych sensorów. Dzięki temu użytkownik może wybrać interesujące go urządzenie. Funk-cja zwraca indeks tablicy wskazujący wy-brany sensor.

Sercem klasy reprezentującej nasz widok jest metoda CGraphView::Draw(). To wła-śnie w tym miejscu wizualizowane są dane odczytywane z akcelerometru. Kod źródło-wy przedstawiający definicję tej metody za-prezentowany jest na Listingu 15.

Kod ten odpowiada za rysowanie trzech wykresów obrazujących zmiany wychyleń sensora ruchu na trzech osiach: X, Y i Z. Przykładowy zrzut ekranu przedstawiający działanie naszej testowej aplikacji można za-obserwować na Rysunku 7.

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

na docelowym urządzeniu, potrzeba jesz-cze szczypty konfiguracji. Na szczęście nie ma tego zbyt dużo. Wszystko, co trzeba zro-bić, to zdefiniować jedno z makr wspiera-nych przez nasz wrapper, oraz dopisać w pli-ku definicji projektu (mmp) odpowiednie bi-blioteki. Na Listingu 16 przedstawiony jest taki plik zawierający konfigurację projektu

zakładającą stosowanie rozszerzenia Sensor Plug-in. Patrząc na ten Listing, należy zwró-cić uwagę na definicję makra ACC_SENSOR_API (ostatnia linia pliku) oraz fragment:

#if defined GCCE

LIBRARY rrsensorapi.lib

#endif

Listing 11. Implementacja prywatnych metod klasy CAccelerometer, zależnych od rozszerzenia

namespace App

{

#if defined(ACC_SENSOR_API)

void CAccelerometer::HandleDataEventL(

TRRSensorInfo aSensor,

TRRSensorEvent aEvent )

{

TInt xAxis = aEvent.iSensorData2;

TInt yAxis = aEvent.iSensorData1;

TInt zAxis = aEvent.iSensorData3;

for ( TInt i = 0; i < iObservers.Count(); ++i )

{

iObservers[i]->OnAccEventL( xAxis, yAxis, zAxis );

}

}

#elif defined (ACC_S60_SENSOR_FRAMEWORK)

void CAccelerometer::DataError(

CSensrvChannel& aChannel,

TSensrvErrorSeverity aError )

{

}

void CAccelerometer::DataReceived(

CSensrvChannel& aChannel,

TInt aCount, TInt aDataLost )

{

if ( aChannel.GetChannelInfo().iChannelType ==

KSensrvChannelTypeIdAccelerometerXYZAxisData )

{

TPckgBuf<TSensrvAccelerometerAxisData> pckg;

aChannel.GetData( pckg );

TSensrvAccelerometerAxisData data = pckg();

for ( TInt i = 0; i < iObservers.Count(); ++i )

{

iObservers[i]->OnAccEventL( data.iAxisX, data.iAxisY, data.iAxisZ

);

}

}

}

void CAccelerometer::GetDataListenerInterfaceL( TUid aInterfaceUid, TAny*&

aInterface )

{

}

#endif

}

Page 72: SDJ Extra 34 Biblia

72

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 73

Występowanie instrukcji warunkowej #if defined w powyższym fragmencie wiąże się z faktem, iż biblioteki do obsługi sen-sora ruchu są dostępne jedynie w wersji na urządzenie (stąd symbol GCCE w ciele wa-runku). W przypadku korzystania z dru-giego rozszerzenia, plik mmp wygląda nie-malże identycznie, z tą różnicą, że w ostat-niej linii definiujemy makro ACC _ S60 _

SENSOR _ FRAMEWORK oraz dołączamy biblio-teki SensrvClient.lib i sensrvutil.lib zamiast rrsensorapi.lib. Dla zainteresowanych oby-

dwie konfiguracje projektów dostępne są w paczce z kodem źródłowym umieszczo-nej na płycie DVD dołączonej do niniejsze-go pisma.

Tych Czytelników, którzy mają dostęp do telefonu działającego pod kontrolą systemu Symbian S60 3rd Edition (lub nowszego), serdecznie zapraszamy do zbudowania i wy-próbowania naszej testowej aplikacji oraz do dalszych eksperymentów z akcelerometrem, bazujących na wykorzystaniu przedstawione-go wyżej opakowania.

Wąż wkracza do akcji!Faktem niezbitym jest, iż do programowa-nia poważnych aplikacji pod Symbian OS stosuje się język C++. Jednakże pisanie ta-kowych aplikacji, szczególnie dla osób po-stronnych, nie jest ani łatwym, ani przy-jemnym zadaniem. Na szczęście dla tych, którzy chcieliby po prostu trochę poeks-perymentować ze swoim telefonem, ist-nieją mniej potężne, ale za to o wiele prost-sze alternatywy. Jedną z takich alternatyw jest możliwość uruchamiania na platfor-mie S60 skryptów pisanych w języku Py-thon. Wprowadzenie do tego ciekawego te-matu było niedawno prezentowane na ła-mach SDJ (numer 2/2009) w artykule pt. Wąż w komórce autorstwa Mariana Witkow-skiego. Dla nas temat ten wydał się intere-sujący z tego względu, iż z poziomu skryp-tu Pythona możemy uzyskać dostęp do da-nych generowanych przez sensor ruchu. Co więcej, da się to zrobić o wiele prościej i szybciej niż w przypadku programu pisa-nego w C++. Innymi słowy, jeśli chciałbyś drogi Czytelniku poeksperymentować z ak-celerometrem bądź stworzyć na szybko ja-kiś mały prototyp aplikacji, a niekoniecz-nie masz ochotę zagłębiać się w szczegóły programowania aplikacji symbianowych w C++, to zapraszamy do dalszej lektury te-go podpunktu.

Jeśli chodzi o proces instalacji i konfigu-racji środowiska deweloperskiego Python for S60, to w Internecie można znaleźć cały sze-reg materiałów na ten temat (patrz: ramka W sieci). My polecamy przeczytanie wspo-mnianego wyżej artykułu autorstwa pana Witkowskiego; artykuł ten można pobrać w postaci elektronicznej za darmo ze stro-ny SDJ (wymagane jest jedynie podanie ad-resu e-mail).

Na Listingu 17 przedstawiona jest imple-mentacja prostej klasy napisanej w języku Py-thon gromadzącej dane odczytywane z senso-ra ruchu.

Schemat działania tej klasy jest bardzo podobny do idei zastosowanej w opisanym wcześniej wrapperze. Przy tworzeniu obiek-tu klasy Accelerometer, w konstruktorze (__init__) pobierany jest obiekt reprezentują-cy sensor ruchu, po czym tworzony obiekt (self) rejestruje się jako obserwator tego sensora. Metoda zwrotna (on_acc_event) odbiera stan sensora i wyłuskuje z niego wartości składowych data_1, data_2 oraz data_3, reprezentujące wychylenia urządze-nia na poszczególnych osiach. Aby uzyskać dostęp do sensora ruchu w skrypcie języka Python, należy zaimportować moduł sensor (import sensor). Instancję przedstawionej wyżej klasy możemy stworzyć przy pomo-cy instrukcji:

acc = Accelerometer()

Listing 12. Zawartość pliku nagłówkowego GraphAppView.hpp, zawierającego interfejs klasy GraphView

#ifndef __INCLUDED_GRAPH_VIEW_HPP__

#define __INCLUDED_GRAPH_VIEW_HPP__

#include <coecntrl.h>

#include <e32base.h>

#include "Accelerometer.hpp"

class CGraphView :

public CCoeControl,

public Acc::MAccelerometerObserver

{

public:

static CGraphView* NewL( const TRect& aRect );

static CGraphView* NewLC( const TRect& aRect );

virtual ~CGraphView();

void Draw( const TRect& aRect ) const;

virtual void SizeChanged();

void OnAccEventL( TInt aAxisX, TInt aAxisY, TInt aAxisZ );

private:

struct TReading

{

inline TReading( TInt aX, TInt aY, TInt aZ )

{

iX = aX;

iY = aY;

iZ = aZ;

}

TInt iX;

TInt iY;

TInt iZ;

};

void ConstructL(const TRect& aRect);

void CreateSensorL();

TInt DisplayPopupListLC( MDesCArray* aArray );

CGraphView();

CArrayFix<TReading>* iArray;

TReading iMax;

TReading iMin;

Acc::CAccelerometer* iSensor;

};

#endif // !defined __INCLUDED_GRAPH_VIEW_HPP__

Page 73: SDJ Extra 34 Biblia

72

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 73

,zaś prosty mechanizm odczytywania da-nych za jej pomocą przedstawiony jest na Li-stingu 18.

Patrząc na zawartość tego Listingu, trze-ba nadmienić, iż programy pisane na plat-formę S60 w Pythonie bazują na tzw. pę-tli aplikacji. Fragment takiej właśnie pętli

przedstawiony jest na wspomnianym Li-stingu. Przed odczytem danych wewnątrz pętli musimy sprawdzić, czy są one do-stępne (stąd biorą się właśnie dwie in-strukcje warunkowe poprzedzające od-czyt danych), po czym możemy odwo-łać się bezpośrednio do składowej tablicy

data, w której komórkach przechowywa-ne są wartości przyśpieszeń na poszczegól-nych osiach.

Przykłady zaprezentowane w tej części artykułu opracowane zostały na podsta-wie skryptu accelball.py autorstwa Chri-stophera Schmidta. Skrypt ten jest ogólno-

Listing 13. Zawartość pliku GraphAppView.cpp

#include <coemain.h>

#include <eikenv.h>

#include <aknpopup.h>

#include <aknlists.h>

#include "GraphAppView.hpp"

namespace

{

const TInt KMargin = 6;

}

CGraphView* CGraphView::NewL( const TRect& aRect )

{

CGraphView* self = CGraphView::NewLC( aRect );

CleanupStack::Pop( self );

return self;

}

CGraphView* CGraphView::NewLC( const TRect& aRect )

{

CGraphView* self = new ( ELeave ) CGraphView;

CleanupStack::PushL( self );

self->ConstructL( aRect );

return self;

}

void CGraphView::ConstructL( const TRect& aRect )

{

iArray = new (ELeave) CArrayFixFlat<TReading>(

aRect.Width() + 2 );

CreateSensorL();

// Create a window for this application view

CreateWindowL();

// Set the windows size

SetRect( aRect );

// Activate the window, which makes it ready to be drawn

ActivateL();

}

CGraphView::CGraphView()

: iMax( KMinTInt, KMinTInt, KMinTInt ),

iMin( KMaxTInt, KMaxTInt, KMaxTInt )

{

}

void CGraphView::CreateSensorL()

{

const TInt KSensorGranularity = 2;

CDesCArrayFlat* array = new (ELeave) CDesCArrayFlat(

KSensorGranularity );

CleanupStack::PushL( array );

Acc::CAccelerometer::GetAvailableSensorsL( array );

if ( array->Count() == 0 )

{

User::Leave( KErrNotFound );

}

TInt sensorIndex( KErrNotFound );

TInt tries = 0;

while ( sensorIndex == KErrNotFound && tries++ < 3 )

{

sensorIndex = DisplayPopupListLC( array );

}

iSensor = Acc::CAccelerometer::NewL( (*array)[

sensorIndex ] );

iSensor->AddObserverL( this );

CleanupStack::PopAndDestroy( array );

}

CGraphView::~CGraphView()

{

delete iSensor;

iSensor = NULL;

delete iArray;

iArray = NULL;

}

void CGraphView::SizeChanged()

{

DrawNow();

}

void CGraphView::OnAccEventL( TInt aAxisX, TInt aAxisY, TInt

aAxisZ )

{

while( iArray->Count() > Rect().Width() )

{

iArray->Delete( 0 );

}

iArray->AppendL( TReading( aAxisX, aAxisY, aAxisZ ) );

iMax.iX = (iMax.iX>aAxisX)?iMax.iX:aAxisX;

iMax.iY = (iMax.iY>aAxisY)?iMax.iY:aAxisY;

iMax.iZ = (iMax.iZ>aAxisZ)?iMax.iZ:aAxisZ;

iMin.iX = (iMin.iX<aAxisX)?iMin.iX:aAxisX;

iMin.iY = (iMin.iY<aAxisY)?iMin.iY:aAxisY;

iMin.iZ = (iMin.iZ<aAxisZ)?iMin.iZ:aAxisZ;

DrawNow();

}

Page 74: SDJ Extra 34 Biblia

74

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 75

dostępny w Internecie (patrz: ramka W sie-ci) i stanowi pełny przykład aplikacji S60 wykorzystującej sensor ruchu za pośred-nictwem języka Python. Zainteresowa-nych Czytelników odsyłamy na stronę au-tora skryptu w celu jego pobrania i szcze-gółowej analizy.

Programowanie to nie wszystko,......można by rzec. Liczy się również, a może przede wszystkim, koncepcja! Zakładamy, iż Czytelnicy niniejszego tekstu, po prze-czytaniu powyższych akapitów, są solid-nie przygotowani do realizacji zadań pro-gramistycznych z zakresu obsługi akcele-rometru w aplikacjach Symbian OS. Acz-kolwiek sama umiejętność oprogramo-wania danej technologii czasami nie wy-starcza. Akcelerometr to stosunkowo no-we rozwiązanie – przynajmniej w kontek-ście rynku aplikacji mobilnych, dlatego też bardzo ważne jest koncepcyjne zrozumie-nie tego, w jaki sposób można wykorzystać jego potencjał w celu uatrakcyjnienia pro-gramów uruchamianych na aparacie ko-mórkowym.

W tym punkcie postaramy się, bazując na analizie istniejących przypadków, zaprezen-tować Czytelnikom garść pomysłów odno-śnie wykorzystania tego urządzenia w prak-tyce. Liczymy, iż pomysły te będą dla Czy-telników źródłem natchnienia przy projek-towaniu własnych aplikacji wykorzystują-cych sensor ruchu. Aby nieco rozluźnić at-mosferę, zacznijmy od omówienia, jakie są potencjalne:

Zastosowania akcelerometru w aplikacjach rozrywkowychGry komputerowe lubi (prawie) każdy. O ile dorosłym osobnikom grać w domu czasami nie wypada (lub zwyczajnie brakuje im na to czasu), o tyle – grając w autobusie czy tram-waju, w poczekalni u lekarza czy w prze-rwie długiego i nudnego zebrania – jak naj-bardziej. A jeżeli przy okazji można się po-chwalić kolegom z pracy swoim nowym, ga-dżeciarskim telefonem komórkowym, to tym bardziej. Dlatego właśnie tzw. gry okazjo-nalne (ang. casual games) przeznaczone do uruchamiania na urządzeniach mobilnych są tak bardzo popularne we współczesnym, zagonionym świecie. Akcelerometr wpaso-

wuje się tutaj idealnie jako nowy rodzaj kon-trolera. Rozważmy konkretny przypadek użycia w postaci gry Labyrinth (patrz: ram-ka W sieci). Któż z nas w młodości nie spę-dził kilku godzin nad małym pudełeczkiem z przezroczystą ścianką, metalowymi kulka-mi oraz dziurkami, do których owe kulki na-leżało doprowadzić? Labyrinth pozwala cie-szyć się podobną zabawą przy wykorzysta-niu telefonu komórkowego (gra jest aktual-nie dostępna na platformy iPhone/iPod To-uch oraz Android). Clue rozgrywki polega na tym, iż telefon traktujemy dokładnie jak opisane wyżej pudełeczko. Na ekranie wy-świetlacza przedstawiona jest kulka poru-szająca się po tytułowym labiryncie, zaś za-daniem gracza jest przeprowadzanie jej do określonej dziurki. Sterowanie w grze odby-wa się właśnie za pomocą akcelerometru: de-likatne wychylenia urządzenia na boki wpra-wiają kulkę w ruch. Zastosowanie prostych zasad fizyki ciała stałego pozwala uzyskać wrażenie, iż obcujemy z prawdziwą kulką zamkniętą w pudełku... Na Rysunku 8 moż-na zobaczyć zrzut ekranu z gry.

Co ciekawe, ta stosunkowo prosta apli-kacja została pobrana ze sklepu Apple już prawie 10 milionów razy! Podobne przy-kłady wykorzystania akcelerometru moż-na mnożyć. Istnieje również cały szereg klasycznych gier (np. wyścigów czy sho-ot'em-up'ów), w których akcelerometr jest wykorzystywany jako alternatywny kon-troler (dla przykładu: wychylenie urzą-dzenia w lewo bądź w prawo powoduje skręcanie/przemieszczanie obiektu, któ-rym steruje gracz). Mówiąc krótko: liczba zastosowań akcelerometru w dziedzinie rozrywki jest w zasadzie nieskończona (je-dynym ograniczeniem jest wyobraźnia au-tora aplikacji).

W tym miejscu należy jednak zdać so-bie sprawę z pewnej kwestii. Otóż gdyby przyszła Ci Czytelniku ochota, aby w ra-mach eksperymentu popróbować imple-mentacji swojej własnej wersji labiryntu, to pamiętaj o tym, że większość tego ty-pu gier bazuje na symulacjach fizycznych. Gdybyś spróbował zmodyfikować naszą testową aplikację (do czego gorąco nama-wiamy) w taki sposób, aby zamiast wy-kresu na ekranie rysowała się kulka prze-mieszczająca się bezpośrednio na podsta-wie przyrostów pobieranych bezpośrednio z urządzania, to wynikowy efekt wyglądał-by nieco kulawo. Kulka ruszałaby się dość niezdarnie, skokowo.

Aby zaimplementować funkcjonal-ność podobną do tej, którą oferuje gra La-birynth, należy zbudować silnik fizycz-ny, który bierze pod uwagę takie aspek-ty jak przyśpieszenie, tarcie itd. Przyro-sty odczytywane z akcelerometru (zazwy-czaj dodatkowo filtrowane bądź uśrednia-

Listing 14. Definicja metody CGraphView::DisplayPopupListLC()

TInt CGraphView::DisplayPopupListLC( MDesCArray* aArray )

{

CAknSinglePopupMenuStyleListBox* list = new( ELeave )

CAknSinglePopupMenuStyleListBox;

CleanupStack::PushL( list );

CAknPopupList* popup = CaknPopupList::NewL(

list, R_AVKON_SOFTKEYS_OK_BACK,

AknPopupLayouts::EMenuWindow);

CleanupStack::PushL( popup );

popup->SetTitleL( _L("Select accelerometer") );

list->ConstructL( popup, CEikListBox::ELeftDownInViewRect );

list->CreateScrollBarFrameL( ETrue );

list->ScrollBarFrame()->SetScrollBarVisibilityL(

CEikScrollBarFrame::EOff,

CEikScrollBarFrame::EAuto);

CTextListBoxModel* model=list->Model();

model->SetItemTextArray( aArray );

model->SetOwnershipType( ELbmDoesNotOwnItemArray );

TInt ret = -1;

if ( popup->ExecuteLD() )

{

ret = list->CurrentItemIndex();

}

CleanupStack::Pop( popup );

CleanupStack::PopAndDestroy( list );

return ret;

}

Page 75: SDJ Extra 34 Biblia

74

Programowanie Symbian OS

SDJ Extra 34 Biblia

Obsługa akcelerometru

www.sdjournal.org 75

Listing 15. Definicja metody CGraphView::DisplayPopupListLC()

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

void CGraphView::Draw( const TRect& /*aRect*/ ) const

{

// Get the standard graphics context

CWindowGc& gc = SystemGc();

// Gets the control's extent

TRect drawRect( Rect());

// Clears the screen

gc.Clear( drawRect );

const TInt h = drawRect.Height();

const TInt rangeX =

((iMax.iX - iMin.iX)==0)?1:(iMax.iX - iMin.iX);

const TInt rangeY =

((iMax.iY - iMin.iY)==0)?1:(iMax.iY - iMin.iY);

const TInt rangeZ =

((iMax.iZ - iMin.iZ)==0)?1:(iMax.iZ - iMin.iZ);

gc.SetBrushColor( KRgbBlack );

gc.SetPenColor( KRgbBlack );

gc.DrawLine( TPoint( 0, h/2 ), TPoint( drawRect.Width(),

h/2 ) );

gc.SetBrushColor( KRgbRed );

gc.SetPenColor( KRgbRed );

TPoint st( 0, h / 2 );

for ( TInt i = 0; i < iArray->Count(); ++i )

{

TInt pos = ( ( iArray->At( i ).iX - iMin.iX )

* ( Rect().Height() - KMargin ) ) / rangeX;

pos = Rect().Height() - pos - ( KMargin / 2 );

gc.DrawLine( st, TPoint( i, pos ) );

st = TPoint( i, pos );

}

gc.SetBrushColor( KRgbGreen );

gc.SetPenColor( KRgbGreen );

st = TPoint( 0, h / 2 );

for ( TInt i = 0; i < iArray->Count(); ++i )

{

TInt pos = ( ( iArray->At( i ).iY - iMin.iY )

* ( Rect().Height() - KMargin ) ) / rangeY;

pos = Rect().Height() - pos - ( KMargin / 2 );

gc.DrawLine( st, TPoint( i, pos ) );

st = TPoint( i, pos );

}

gc.SetBrushColor( KRgbBlue );

gc.SetPenColor( KRgbBlue );

st = TPoint( 0, h / 2 );

for ( TInt i = 0; i < iArray->Count(); ++i )

{

TInt pos = ( ( iArray->At( i ).iZ - iMin.iZ )

* ( Rect().Height() - KMargin ) ) / rangeZ;

pos = Rect().Height() - pos - ( KMargin / 2 );

gc.DrawLine( st, TPoint( i, pos ) );

st = TPoint( i, pos );

}

}

TARGET Graph.exe

TARGETTYPE exe

UID 0x100039CE 0xA0005A7D

SECUREID 0xA0005A7D

EPOCSTACKSIZE 0x5000

SOURCEPATH ..\..\src\graph

SOURCE main.cpp

SOURCE GraphApplication.cpp

SOURCE GraphAppView.cpp

SOURCE GraphAppUi.cpp

SOURCE GraphDocument.cpp

SOURCEPATH ..\..\src\accelerometer

SOURCE Accelerometer.cpp

SOURCEPATH .

START RESOURCE Graph.rss

HEADER

TARGETPATH resource\apps

END //RESOURCE

START RESOURCE Graph_reg.rss

#ifdef WINSCW

TARGETPATH \private\10003a3f\apps

#else

TARGETPATH \private\10003a3f\import\apps

#endif

END //RESOURCE

USERINCLUDE ..\..\src\graph

USERINCLUDE ..\..\src\accelerometer

SYSTEMINCLUDE \epoc32\include

LIBRARY euser.lib

LIBRARY apparc.lib

LIBRARY cone.lib

LIBRARY eikcore.lib

LIBRARY avkon.lib

LIBRARY commonengine.lib

LIBRARY efsrv.lib

LIBRARY estor.lib

LIBRARY eikcoctl.lib

LIBRARY eikctl.lib

LIBRARY bafl.lib

#if defined GCCE

LIBRARY rrsensorapi.lib

#endif

LANG SC

VENDORID 0

CAPABILITY NONE

MACRO ACC_SENSOR_API

Page 76: SDJ Extra 34 Biblia

76

Programowanie Symbian OS

SDJ Extra 34 Biblia

ne) traktowane są z punktu widzenia ta-kiego silnika jako wektory siły przykłada-ne do kulki.

Zastosowania w aplikacjach użytkowychW przypadku aplikacji użytkowych akcelero-metr również znalazł cały szereg zastosowań. Jako jeden z najprostszych przypadków uży-cia można podać aplikację ShutUp, co na ję-zyk polski można przetłumaczyć jako Za-mknijSię. Ta działająca w tle aplikacja pozwa-la uciszyć telefon (np. alarm ustawiony w bu-dziku) poprzez... fizyczne przewrócenie te-lefonu do góry nogami. Co ciekawe, razem z wprowadzeniem do telefonów mobilnych akcelerometru pojawiły się całkiem nowe ka-tegorie aplikacji użytkowych. Jedną z takich kategorii są programy wspierające utrzymy-wanie dobrej kondycji, np. liczniki kroków bądź kalorii spalanych podczas spaceru. Inna, nowa rodzina aplikacji, korzystająca dość in-tensywnie z dobrodziejstw akcelerometru, to

tzw. programy-gadżety. Są to zazwyczaj drob-ne aplikacje, które nie wnoszą specjalnej war-tości użytkowej; służą raczej jako zabawki, którymi można pochwalić się przed znajo-mymi. Znakomitym przykładem takiego pro-gramu jest iMilk, czyli aplikacja symulująca... szklankę mleka. Dwa przykładowe zrzuty ekranu z tej aplikacji znajdują się odpowied-nio na Rysunkach 9 i 10.

Poruszanie urządzeniem (jak szklanką) powoduje wylewanie się wirtualnego napoju. Aplikacje-gadżety są nie lada gratką dla agen-cji reklamowych, stosuje się je często jako ma-teriały marketingowe (przykładem może być aplikacja podobna do iMilk reklamująca mar-kę popularnego piwa).

PodsumowanieW powyższym artykule przedstawiliśmy te-mat obsługi akcelerometru przy tworzeniu aplikacji Symbian OS dla platformy S60. Główny nacisk został położony na wykorzy-stanie dwóch najważniejszych API do ob-

sługi tego urządzenia dostępnych z pozio-mu języka C++. W ramach rozwinięcia te-matu przedstawiliśmy implementację wrap-pera opakowującego wspomniane interfejsy programistyczne. Pokazaliśmy również przy-kład użycia wspomnianego wrappera bazują-cy na prostej, testowej aplikacji. Jako interesu-jącą alternatywę dla dość skomplikowanych API dostępnych z poziomu C++, przedstawi-liśmy możliwość oprogramowania akcelero-metru w telefonach działających pod kontro-lą Symbian OS z poziomu skryptu języka Py-thon. W ramach podsumowania tematu opi-saliśmy szereg istniejących aplikacji wykorzy-stujących sensor ruchu.

Mamy głęboką nadzieję, iż niniejszy arty-kuł zainteresuje Czytelników tematem akce-lerometru i pomoże im wykorzystać to in-nowacyjne rozwiązanie we własnych apli-kacjach.

Listing 17. Wykorzystanie Python for S60: definicja klasy Accelerometer

class Accelerometer(object):

data = []

def __init__(self):

acc = sensor.sensors()['AccSensor']

self.s = sensor.Sensor(acc['id'], acc['category'])

self.s.connect(self.on_acc_event)

def on_acc_event(self, state):

self.data = []

for key in ['data_1', 'data_2', 'data_3']:

val = state[key]

self.data.append(val)

def cleanup(self):

self.s.disconnect()

Listing 18. Wykorzystanie Python for S60: odczytywanie danych z obiektu reprezentującego akcelerometer

while running:

# ...

if not acc: continue

if not len(sense_conn.delta): continue

x = acc.data[0]

y = acc.data[1]

z = acc.data[2]

# ...

acc.cleanup()

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.

RAFAŁ KOCISZPracuje na stanowisku Dyrektora Techniczne-go w firmie Gamelion, wchodzącej w skład Gru-py BLStream. Rafał specjalizuje się w technolo-giach związanych z produkcją oprogramowa-nia na platformy mobilne, ze szczególnym na-ciskiem na tworzenie gier. Grupa BLStream po-wstała, by efektywniej wykorzystywać potencjał dwóch szybko rozwijających się producentów oprogramowania – BLStream i Gamelion. Firmy wchodzące w skład grupy specjalizują się w wy-twarzaniu oprogramowania dla klientów korpo-racyjnych, w rozwiązaniach mobilnych oraz pro-dukcji i testowaniu gier. Kontakt z autorem: [email protected]

WOJCIECH GASEKPracuje na stanowisku Starszego Programisty Gier Natywnych w firmie Gamelion, wchodzą-cej w skład Grupy BLStream. Wojciech specjali-zuje się w technologiach związanych z produk-cją oprogramowania na platformy mobilne, ze szczególnym naciskiem na tworzenie gier. Gru-pa BLStream powstała, by efektywniej wykorzy-stywać potencjał dwóch szybko rozwijających się producentów oprogramowania – BLStream i Ga-melion. Firmy wchodzące w skład grupy specja-lizują się w wytwarzaniu oprogramowania dla klientów korporacyjnych, w rozwiązaniach mo-bilnych oraz produkcji i testowaniu gier. Kontakt z autorem: [email protected]

Page 77: SDJ Extra 34 Biblia
Page 78: SDJ Extra 34 Biblia

78

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 79

Biblioteka SDL (Simple DirectMedia Layer) jest wieloplatformową war-stwą abstrakcji sprzętowej zapewnia-

jącą niskopoziomowy dostęp do urządzeń dźwiękowych, wideo, klawiatury, myszy itp. Na liście oficjalnie wspieranych systemów operacyjnych znajdują się między innymi: Windows, Linux, Mac OS, BeOS, różne od-miany BSD, Solaris. Biblioteka nieoficjalnie wspiera również takie systemy jak Amiga OS, OS/2, czy też interesujący nas w kontekście tego artykułu – Symbian OS.

Rys historycznyCofnijmy się do początków roku 1998, tak gdzieś w okolice lutego albo marca. Szczy-towym osiągnięciem w dziedzinie GUI jest Windows 95. Windows 98 znane jest głów-nie w kontekście szybko rozpowszechniają-cego się filmu (o YouTube nikt nawet wte-dy nie myślał), na którym Bill Gates robi dobrą minę do blue screena po próbie pod-łączenia skanera USB. DirectX znajduje się gdzieś pomiędzy wersją 3.0 a 5.0 – bez re-welacji, ale wiadomo już, że Windows do prawdziwych gier jednak się nadaje. Ma-my nawet akcelerację grafiki trójwymiaro-wej – dopiero co wypuszczony został układ Voodoo2.

Tymczasem pod Linuksem, pod Unik-sami w ogólności, tryumfy święci konso-la tekstowa. I nic dziwnego, bo z rozwią-zań okienkowych mamy takie perełki jak TWM, FVWM czy też Window Maker (no, ten jakoś ujdzie). Na pierwsze wersje śro-dowiska KDE czy GNOME przyjdzie nam jeszcze kilka miesięcy poczekać, dużo one zresztą nie zmienią; na Rysunku 1 można sobie obejrzeć, jak to to wyglądało. Nawia-sem mówiąc, ówczesnym szczytem mody było używanie schematu graficznego na-śladującego Windows 95.

Linuksowe gry ograniczają się do bezpo-średniego wykorzystywania bibliotek X11, na dodatek w wariancie, w którym wszyst-kie lokalne operacje odbywają się przez sieć. Podczas gdy cały świat gra w Diablo czy Starcrafta, fanatycy „lepszego” syste-mu mogą co najwyżej popykać w XBilla, Xchompa czy XEvil (Rysunek 2). Istniały, co prawda, jakieś tam próby stworzenia bi-bliotek, które ułatwiałyby tworzenie gier, ale bardziej przypominało to ostatnie po-drygi konającej ostrygi, niż skoordynowane działania. Z ciekawszych opcji można wy-mienić bibliotekę SVGALib, która umożli-wiała bezpośredni dostęp do karty graficz-nej, jednak nic poza tym. Uzyskanie wspar-cia dla nowszych kart graficznych było pro-blematyczne, nie istniała również możli-wość jej wykorzystania pod iksami. Biblio-teka GGI, swego czasu zapowiadająca się dosyć interesująco, nigdy nie wyszła poza stadium ciekawostki. Popularna pod DOS-

em biblioteka Allegro dopiero raczkowała z obsługą innych systemów operacyjnych, a gdy dorobiła się już jako-tako działającej wersji, nie zdobyła zbyt wielkiej popularno-ści. Co-to-nie-potrafiąca biblioteka PLIB zo-stała wykorzystana w kilku ambitnie zapo-wiadających się projektach, później słuch o niej (i o tamtych projektach) zaginął.

Sytuacja nie wygląda zbyt ciekawie. Mro-wie bibliotek i brak jednoznacznego lide-ra nie zachęca do tworzenia oprogramowa-nia, szczególnie w kontraście do będącego standardem na Windowsach DirectX.

Pracując nad windowsowym portem ską-dinąd znanego emulatora Macintosha Exe-cutor, Sam Lantinga zauważył prostą zasa-dę: pewna część funkcjonalności była im-plementowana w ten sam sposób na każ-dej platformie sprzętowej. Zawsze należa-ło zapewnić pewien sposób na dobranie się do ekranu, dźwięku, urządzeń wejścio-wych. Stąd bardzo blisko było do idei stwo-rzenia wieloplatformowej biblioteki mają-cej za zadanie umożliwienie dostępu do tych zasobów. Dzięki takiemu podejściu programista zamiast zawracać sobie głowę szczegółami implementacyjnymi DirectX, libX11, SVGAlib mógłby wykorzystać jed-nolite API, a za niskopoziomowe operacje właściwe dla konkretnej platformy odpo-wiadałaby biblioteka.

Tak narodził się Simple DirectMedia Layer. Początkowo obsługiwanych plat-form sprzętowych nie było zbyt wiele; tyl-ko Windows i Linux. Szybko dołączył do nich BeOS, wspaniale zapowiadający się system operacyjny, o którym dzisiaj nikt nie pamięta. Na nadmiar aplikacji wyko-rzystujących SDL-a również nie można by-ło narzekać. Wspomniany wcześniej emu-lator Executor, mało znany shareware (Ma-elstrom), jak również Doom, który w wer-sji wykorzystującej inne biblioteki dostęp-ny był od 4 lat. Czemu więc SDL nie został

SDL na Symbianie

W jaki sposób można wykorzystać jedną z najlepszych i najszerzej wykorzystywanych bibliotek ułatwiających tworzenie gier? Czy jest dostępna jej implementacja na Symbiana? Na co należy zwrócić szczególną uwagę podczas jej użytkowania? Odpowiedzi na wszystkie te pytania (i nie tylko) znajdują się poniżej.

Dowiesz się:• Niezbędnego minimum na temat programo-

wania z wykorzystaniem biblioteki SDL;• Jak przedstawia się sytuacja z używaniem

SDL-a na Symbianie;• Jakie są plusy i minusy istniejących rozwiązań.

Powinieneś wiedzieć:• Co nieco na temat telefonów z Symbianem

na pokładzie;• Jak się programuje w C/C++.

Poziom trudności

Cztery porty

Page 79: SDJ Extra 34 Biblia

78

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 79

tylko jedną z wielu dostępnych bibliotek Loki Software?

Firma Loki Software, założona w sierpniu 1998 roku, zatrudnia Sama Lantingę na sta-nowisku lead developera. Pomysłem na biz-nes, jak się później okazało dosyć kiepskim, było portowanie windowsowych gier na Li-nuksa, idea swoją drogą zapożyczona z prze-mysłu filmowego, gdzie analogią gry na Win-dowsie miał być film w kinie i odpowiednio gry na Linuksie – film na kasecie lub płycie DVD. Dzięki obsadzeniu kluczowego stano-wiska przez autora SDL-a gry zyskiwały so-lidną podstawę systemową, a rozwój biblio-teki miał mocne plecy zapewnione przez wsparcie finansowe ze strony firmy. Należy tu wspomnieć, że mimo zaangażowania Lo-ki kod źródłowy Simple DirectMedia Layer przez cały czas pozostawał otwarty.

Reszta jest historią. Loki przeportowało wiele głośnych tytułów, jak chociażby Heroes of Might & Magic III, Railroad Tycoon II, De-scent 3, Unreal Tournament, Rune czy Postal, a biblioteka SDL udowodniła, że jak najbar-dziej nadaje się do poważnych zastosowań. W chwili upadku Loki Games w styczniu 2002 roku Simple DirectMedia Layer był już de facto standardem, a znaczenie pozostałych bibliotek zostało zmarginalizowane.

Z czym to się je?Jeżeli SDL jest taką rewelacją, to z pewno-ścią chcielibyśmy zobaczyć jakiś kawałek kodu źródłowego, żeby się przekonać, jak to naprawdę wygląda. Listing 1 przedsta-wia prosty program, który otwiera okienko i czeka na naciśnięcie dowolnego klawisza. Polecenie kompilacji jest proste:

g++ sdl.cpp `sdl-config --libs --cflags`

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

#include <SDL.h>

Jedyny nagłówek biblioteki, jaki trzeba wskazać preprocesorowi.

int main( int argc, char** argv )

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

SDL_Init( SDL_INIT_VIDEO );

atexit( SDL_Quit );

Za pomocą pierwszej linii inicjalizujemy bi-bliotekę i deklarujemy, że będziemy wyko-rzystywali podsystem graficzny. Nie przej-mujemy się obsługą błędów (zostawmy to ja-ko zadanie domowe dla czytelnika). Druga linia zapewnia poprawne uprzątnięcie zaso-bów podczas wychodzenia z programu.

SDL_Surface* screen = SDL_SetVideoMode(

256, 256, 32, 0 );

Tworzymy specjalną powierzchnię gra-ficzną – jej zawartość będzie wyświetlana w oknie aplikacji. Chcemy uzyskać okno o rozmiarach 256×256 pikseli i o 32-bi-towej głębi kolorów. Ostatni parametr to flagi, które nas w tym momencie nie in-teresują.

SDL_Event event;

while( SDL_PollEvent( &event ) ) { ... }

W nieskończonej pętli odpytujemy kolej-kę zdarzeń SDL-a. Należy upewnić się, że przy każdym odpytywaniu kolejka zosta-nie całkowicie opróżniona (warunek pę-tli while). Jednorazowe wywołanie SDL _

PollEvent() jest niepoprawne!

switch( event.type )

{

case SDL_QUIT:

case SDL_KEYDOWN:

return 0;

...

Jeżeli typem zdarzenia jest żądanie wyjścia (na przykład po naciśnięciu przycisku za-

mknięcia okna), lub naciśnięcie klawisza klawiatury – wyjdź z funkcji (a zatem też z programu).

Łopatologiczne wyłożenie w zasadzie ba-nalnego kodu było w tym momencie nie-zmiernie ważne. Zrozumienie działania te-go programu jest równoznaczne ze zrozu-mieniem działania całego SDL-a. Program jest cały czas aktywny – nie ma tu typowej dla Symbiana reakcji na timery, nie ma też charakterystycznego dla środowisk graficz-nych typu GTK czy QT podłączania sygna-łów, które później powodują podjęcie odpo-wiednich akcji.

Przykład bardziej kompletnyNa Listingu 2 przedstawiony został poprzed-nio omawiany program rozszerzony o funkcję rysującą na ekranie. Do głównej pętli progra-mu dodany został następujący fragment kodu:

if( SDL_LockSurface( screen ) == 0 )

{

Draw( screen );

SDL_UnlockSurface( screen );

SDL_Flip( screen );

}

Funkcja SDL _ LockSurface udostępnia nam bezpośredni wskaźnik do „pamię-ci ekranu” (składowa pixels). W zależno-ści od implementacji i konfiguracji środo-wiska może się za tym kryć udostępnienie tymczasowego bufora, konwersja systemo-wego formatu piksela na żądany podczas tworzenia powierzchni graficznej, lub na-wet brak jakiejkolwiek operacji. Każda za-blokowana powierzchnia musi być później

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

Page 80: SDJ Extra 34 Biblia

80

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 81

odblokowana za pomocą polecenia SDL _

UnlockSurface. Funkcja SDL _ Flip odpo-wiedzialna jest za zamianę buforów, ie. za wyświetlenie zmian wykonanych na po-wierzchni na ekranie.

W procedurze Draw pobierany jest wskaź-nik do pamięci ekranu (surface->pixels), który rzutujemy na 32-bity, które odpowia-dają jednemu pikselowi (podczas tworzenia powierzchni ekranowej zażądaliśmy 32-bi-towej głębi kolorów). W pętli wypełniamy całą powierzchnię okna wzorem graficz-nym przedstawionym na Rysunku 4. Nale-ży tu zwrócić uwagę, że omawiany kod bę-dzie działał poprawnie tylko na architektu-rach little endian, gdzie formatem koloru 32-bitowego jest 0xAARRGGBB. Funkcja SDL_GetTicks zwraca liczbę milisekund od czasu inicjalizacji programu, co wykorzysta-ne jest w celu umożliwienia animacji koloru niebieskiego.

Dodatkowe bibliotekiKtoś może zapytać, czemu rysowanie po ekranie odbywa się w dosyć zagmatwany sposób. Manipulowanie wskaźnikami, ope-racje bitowe, zbyt czytelne to nie jest. Powo-dów jest kilka:

• szybkość;• mały rozmiar kodu;• szybkość;• brak zależności od innych bibliotek;• szybkość.

Funkcje typu PutPixel, które przy każdym wywołaniu muszą obliczać offset do pikse-la, nigdy nie będą tak szybkie jak sekwen-cyjny zapis do pamięci. Kod pisany dla jed-nego specyficznego zastosowania nie musi obsługiwać wielu różnych formatów pikseli, co się z tym również wiąże, nie musi wyko-nywać konwersji koloru podawanego zazwy-

czaj w wyskalowanych do 0 – 255 osobnych składowych R, G, B do formatu właściwego dla powierzchni graficznej. Zwrócenie uwa-gi na wydajność kodu jest szczególnie waż-ne na platformach mobilnych, gdzie proce-sory są z reguły dosyć wolne, w celu ograni-czenia zużycia baterii. Przykładową proce-durę implementującą funkcjonalność Put-Pixel (SDL jej nie posiada!) można obejrzeć pod adresem http://www.libsdl.org/intro.en/usingvideo.html.

Jak można wywnioskować z powyższego przykładu, funkcjonalność biblioteki SDL jest stosunkowo prosta, jednak nie można powie-dzieć, że niewystarczająca. Jeżeli komuś bra-kuje pewnych możliwości, być może nie zro-zumiał jej założeń projektowych. Na szczęście dla osób pragnących bardziej wysokopoziomo-wego podejścia istnieje kilka bibliotek uzupeł-niających funkcjonalność SDL-a.

SDL_gfxBiblioteka 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.

SDL_imageSimple DirectMedia Layer potrafi wczyty-wać z dysku tylko obrazki zapisane w for-macie BMP. Biblioteka SDL_image znacz-nie poszerza tę funkcjonalność, dodając obsługę między innymi formatów PNG, TGA, TIFF, JPEG, GIF. Ładowanie zostało ujednolicone do funkcji IMG_Load.

Niektóre z projektów używających SDL_image: enigma, Battle for Wesnoth, Rocks'n'Diamonds, Mirror Magic, Liqu-id War 6.

SDL_mixerSDL implementuje bardzo podstawowe urządzenie audio. Programy chcące od-twarzać dźwięki dostają kilkadziesiąt ra-zy na sekundę pewien mały bufor do wy-pełnienia danymi audio w zadanym forma-cie. Granie dwóch dźwięków na raz lub po-trzeba dostosowania formatu dźwięku wią-że się z koniecznością samodzielnego napi-sania miksera lub wykorzystania biblioteki 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-

Listing 1. Prościutki program

#include <SDL.h>

int main( int argc, char** argv )

{

SDL_Init( SDL_INIT_VIDEO );

atexit( SDL_Quit );

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

for(;;)

{

SDL_Event event;

while( SDL_PollEvent( &event ) )

{

switch( event.type )

{

case SDL_QUIT:

case SDL_KEYDOWN:

return 0;

default:

break;

}

}

}

}

Rysunek 2. Typowa linuksowa gra z 1998 roku (http://www.games.ru/games/linux/screenshots/xevil.gif)

Page 81: SDJ Extra 34 Biblia

80

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 81

za tym mikser posiada pewne wsparcie dla podstawowych efektów dźwiękowych.

Wybrane programy korzystające z tej bi-blioteki: Rise of the Triad, Pushover, Hed-gewars, Jump & Bump, Trackballs.

SDL_netBiblioteka rozszerzająca funkcjonalność SDL o obsługę sieci. Obsługuje konwersję danych z/na format sieciowy, rozwiązywanie nazw, gniazda TCP/UDP itp., udostępniając wspól-ny dla wszystkich platform interfejs.

Funkcjonalność udostępnianą przez SDL_net jest wymagana przez: Widelands, Warzone 2100, Maelstrom.

SDL_soundJeżeli funkcjonalność udostępniana przez bibliotekę SDL_mixer nam nie odpowiada, możemy sami zaimplementować miksowa-nie, a ładowanie dźwięków powierzyć biblio-

tece SDL_sound. Lista obsługiwanych forma-tów jest znacznie bardziej obszerna, doszły tu na przykład Speex, FLAC, AU, AIFF, IT, XM, S3M i wiele innych.

Biblioteka SDL_sound wykorzystana zo-stała w projektach: DOSBox, Advanced Stra-tegic Command.

SDL_ttfBiblioteka SDL_ttf umożliwia wykorzystanie fontów TrueType w programach korzystających

z Simple DirectMedia Layer. Sama rasteryzacja znaków odbywa się za pomocą biblioteki Fre-etype. Pośród udostępnianych funkcji znajdu-je się pobieranie metadanych takich jak metry-ka czcionki, rozmiar zadanego tekstu, jak rów-nież rysowanie na ekranie z antyaliasingiem za-równo włączonym, jak i wyłączonym. Bibliote-ka potrafi składać tekst z kerningiem.

SDL_ttf użyte zostało między innymi w programach: X-Moto, GNU Robbo, UFO: Alien Invasion, Tux Paint.

Rysunek 4. Wzór graficzny rysowany przez program z Listingu 2

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

Rysunek 3. Wynik działania programu z Listingu 1

Listing 2. Program rozszerzony

#include <SDL.h>

void Draw( SDL_Surface* surface )

{

Uint32* dst = (Uint32*)surface->pixels;

Uint32 ticks = ( SDL_GetTicks() / 4 ) & 0x1FF;

if( ticks & 0x100 )

{

ticks = 511 - ticks;

}

for( int y = 0; y < 256; y++ )

{

for( int x = 0; x < 256; x++ )

{

*dst++ = ( x << 16 ) | ( y << 8 ) | ticks;

}

}

}

int main( int argc, char** argv )

{

SDL_Init( SDL_INIT_VIDEO );

atexit( SDL_Quit );

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

for(;;)

{

SDL_Event event;

while( SDL_PollEvent( &event ) )

{

switch( event.type )

{

case SDL_QUIT:

case SDL_KEYDOWN:

return 0;

default:

break;

}

}

if( SDL_LockSurface( screen ) == 0 )

{

Draw( screen );

SDL_UnlockSurface( screen );

SDL_Flip( screen );

}

}

}

Page 82: SDJ Extra 34 Biblia

82

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 83

Oczywiście nie są to wszystkie bibliote-ki możliwe do wykorzystania razem z SDL-em, dużo bardziej kompletną listę można znaleźć pod adresem http://www.libsdl.org/libraries.php.

Wewnętrzna budowaSimple DirectMedia Layer składa się z kilku w miarę niezależnych od siebie modułów. Podział na niezależne części jest w pewnym stopniu widoczny w wywołaniu funkcji SDL_Init, która przyjmuje listę podsystemów do zainicjalizowania. Źródła pogrupowane są w następujące katalogi:

• audio – wszystko, co związane z dźwiękiem;

• cdrom – obsługa CD-ROM-u: odtwa-rzanie audio, wysuwanie tacki;

• cpuinfo – rozpoznawanie typu procesora;• events – obsługa zdarzeń;• file – wewnętrzna obsługa plików;• hermes – biblioteka konwersji formatów

pikseli;

• joystick – obsługa joysticka;• loadso – dynamiczne wczytywanie bi-

bliotek podczas działania programu;• main – wrapper implementujący pod-

stawową aplikację właściwą dla danej platformy;

• stdlib – implementacja pewnej części funkcjonalności standardowych bi-bliotek, które nie wszędzie mogą być dostępne;

• thread – obsługa wątków;• timer – funkcjonalność związana z

obsługą czasu;• video – dosyć złożony podsystem od-

powiedzialny nie tylko za grafikę, ale również za obsługę urządzeń wejścio-wych (mysz, klawiatura) i zdarzeń.

W większości katalogów znajduje się kil-ka niewielkich plików źródłowych defi-niujących wspólny interfejs danego mo-dułu, a implementacje właściwe dla kon-kretnej platformy znajdują się w podka-talogach.

Podsystemy, które przed użyciem należy zainicjalizować za pomocą SDL_Init to: ti-mer, audio, video, cdrom, joystick.

A co z Symbianem?Symbianowy port Simple DirectMedia Layer jest sprawą dosyć skomplikowaną. Ale zacznijmy od początku.

W roku 2001 Hannu Viitala udostępnia port biblioteki SDL na platformę EPOC i przy jego pomocy tworzy port Frodo, emu-latora C64 na pierwszy symbianowy tele-fon – Nokię 9210 (Rysunek 5). Wersja ta miała spore braki – jak na przykład brak jakiejkolwiek obsługi dźwięku. Proble-mem było z pewnością również wsparcie różnych urządzeń, zaglądając w kod moż-na zauważyć brak rozdzielczości typo-wych dla symbianowych urządzeń pierw-szej i drugiej edycji, przykładowo 176x208 – 9210 to komunikator z niestandardo-wą rozdzielczością. Niemniej był to kawa-łek kodu otwierający przed programista-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 w dniu dzisiejszym (wersja biblioteki ze 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-miany kodu między wersjami, przykłado-Rysunek 7. Rick Dangerous w rozdzielczościach 240x320 i 320x240

Rysunek 6. CDoom na Nokii 9210. Bramy piekieł otworzyły się 11 września 2001 roku (http://doom.wikia.com/wiki/File:Nokia9210cdoom.jpg)

Page 83: SDJ Extra 34 Biblia

82

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 83

wo wersja Mertamy zawiera moduł obsługi joysticka napisany przez Perssona.

Czwarty port został stworzony przez au-tora tego artykułu z powodu rażących bra-ków funkcjonalności, niejasnej dokumen-tacji, niesatysfakcjonującej wydajności i wielu drobnych błędów w istniejących roz-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-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 światopogląd na niektóre rzeczy, poza tym pracuje w Nokii. Ja – no cóż – mam najlep-szą wersję SDL-a na Symbiana. Przynajm-niej dla mnie.

Przygoda z moją wersją kodu zaczęła się od próby odpalenia na telefonie gry Rick Dangerous, klasyka z ośmio- i szesnastobi-towców. Kod źródłowy klona gry można by-

ło pobrać ze strony http://www.bigorno.net/xrick/, samo portowanie poza drobny-mi problemami z ilością dostępnej pamię-ci przebiegło stosunkowo bezproblemowo. Niemniej, na tak wczesnym etapie pojawi-ły się już pewne zgrzyty związane z wyko-rzystaniem portu Mertamy. Przede wszyst-

kim – xrick pracował w rozdzielczości 320x200, a wyświetlacze telefonów mają rozdzielczość 320x240. W takim wypad-ku poprawnym i jedynym akceptowalnym dla mnie rozwiązaniem było wyświetlanie gry z zachowaniem odwzorowania pikse-li 1:1, oraz wypełnienie pustej przestrzeni

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

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

portrait

Rysunek 9. ScummVM odpalony w orientacji landscape

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

Śmiechu warteZobaczmy, co takiego o historii powstania SDL-a piszą na Wikipedii (http://en.wikipedia.org/wiki/Simple_DirectMedia_Layer):Sam Lantinga created the library, first releasing it in early 1998, while working for Loki Software. He got the idea while porting a Windows application to Macintosh. He then used SDL to port Do-om to BeOS (see Doom source ports).

No... Nie w Moskwie, tylko w Leningradzie, nie samochody, tylko rowery, i nie rozdają, tylko kradną, a poza tym wszystko się zgadza.

Page 84: SDJ Extra 34 Biblia

84

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 85

kolorem czarnym. Niestety, SDL rozciągał obraz wyświetlany przez grę do pełnej roz-dzielczości ekranu, co po pierwsze wyglą-dało niezbyt ładnie (niektóre linie były po-wielone, inne nie), po drugie zaburzało pro-porcje, a po trzecie było okropnie wolne. Pro-blem został ominięty przez rozszerzenie po-wierzchni ekranowej, na której gra się ry-suje do rozdzielczości wyświetlacza telefo-nu. Nie było to jednak zupełne rozwiąza-nie problemu – SDL nie potrafił przełączyć orientacji telefonu z domyślnej 240x320 na 320x240, co wymagało uruchomienia pro-gramu typu RotateMe przed włączeniem samej gry. Jeżeli ten krok pominąć, mogli-śmy się delektować cudem widocznym na Rysunku 7 (następnego dnia cud posiniał i obrzękł). Problemy zostały zgłoszone auto-rowi portu SDL-a, niestety odpowiedź by-ła dalece niesatysfakcjonująca, chociaż kil-ka pomniejszych błędów – jak na przykład niepoprawna implementacja funkcji SDL_ListModes zostało naprawionych.

Dalszy rozwój xricka został przeze mnie porzucony, pojawiła się zresztą konkuren-cyjna implementacja wyposażona w me-nu, z poziomu którego można było zmie-nić orientację telefonu, zmienić opcje ska-lowania itp. niepotrzebne w/g mnie prze-szkadzajki. Projektem, którym bardziej się zainteresowałem od strony programistycz-nej, został OpenTTD – klon (czy też może implementacja na podstawie deasemblowa-nego kodu wzbogacona o wiele poprawek i udoskonaleń) gry Transport Tycoon Delu-xe. Pierwsza wersja działająca na S60 zo-stała opublikowana po niecałym miesiącu prac, pod koniec stycznia 2008 roku. Pod-czas dalszego rozwoju projektu niedosko-nałości SDL-a Mertamy tak dały mi się we znaki, jak również wymiana maili była na tyle nieowocna, że w lipcu 2008 zacząłem rozwijać swoje własne odgałęzienie biblio-teki. Kod bazuje na bibliotece SDL w wersji

1.2.13 (najnowsza w chwili pisania tego ar-tykułu) i porcie Mertamy w wersji 2.2.0.

EKA1?Pierwszą, bardzo poważną zmianą by-ło usunięcie wsparcia dla S60 pierwszej i drugiej edycji (kernel EKA1), które z mo-jego punktu widzenia było niepotrzebnym balastem. Platforma jest praktycznie rzecz biorąc martwa, co więcej, dostępny sprzęt znacznie ogranicza możliwe zastosowania – w rozdzielczości 176x208, z kilkudzie-sięcio megahertzowym procesorem i kilko-ma megabajtami pamięci się nie zaszaleje. Poza tym – jak testować, czy zmiany w bi-bliotece będą się chociażby kompilowały ze starym kodem? EKA1 musiało odejść.

Wartym wspomnienia udogodnieniem wyłącznym dla EKA2 (S60 trzeciej i piątej edycji) jest dostępność bibliotek OpenC i OpenC++. Dzięki nim można w miarę bez-problemowo wykorzystywać standardowe funkcje POSIX, STL i podzbiór Boosta.

Biblioteka statyczna czy dynamiczna?Mertama dostarcza swoją bibliotekę w for-mie dynamicznie linkowanej. Ma to mo-że jakieś tam zalety, ale jest również po-ważna wada: GCC 3.4 (jedyne wspierane przez Nokię) ma błąd, który uniemożliwia poprawne zbudowanie dll-ki, w której jest jakieś tam statyczne mambo-dżambo (nie-zbyt pamiętam, o co tam chodziło). Jedy-ną alternatywą jest wykorzystanie płatne-go kompilatora RVCT. Dostarczanie bi-blioteki dynamicznej wymaga też zacho-wania kompatybilności binarnej, co może być czasem zbyt mocno wiążące ręce. Po-za tym, mimo zapewnień Mertamy, na fo-rach można znaleźć raporty o problemach ze współpracą symbianowego OpenTTD z C2Doomem. Biorąc wszystko powyższe pod uwagę, decyzja mogła być tylko jedna – mój SDL będzie statycznie linkowany.

Czyszczenie koduStan projektu można było opisać jednym słowem: bagno. Dwa nigdzie nie wykorzy-stywane śmiecio-pliki źródłowe mające w sumie 55 KB i wprowadzające zamiesza-nie. Pięć różnych styli formatowania kodu w obrębie 20 linii w jednym pliku. Mnó-stwo zakomentowanego kodu nieznanego przeznaczenia. #ifdefy bez żadnego opisu i sensu. Sześciokilobajtowa reimplemen-tacja std::queue (na dodatek zrealizowa-na Symbian-way). Komentarze stwierdza-jące rzeczy oczywiste (includes, forward declarations). Przekombinowana hierar-chia klas, która sensu żadnego co prawda nie ma, za to jest bardzo obiektowa. Więk-szość kodu została oczyszczona.

Wirtualny wskaźnik myszyKuriozum pozbawione jakiegokolwiek użytku: po naciśnięciu zielonej słuchawki na ekranie telefonu pokazywał się wskaź-nik myszy przydatny zupełnie do nicze-go. Trafienie nim w konkretny punkt by-ło trudne bądź niemożliwe (duży skok), a przesuwanie na drugi koniec ekranu uciążliwe (brak akceleracji ruchu). Klika-nie również zostało zrealizowane bez po-myślunku: pierwsze kliknięcie przesuwa-ło prawdziwy kursor myszy w miejsce wir-tualnego, a dopiero drugie było rzeczywi-stym kliknięciem, ale przez program wi-dziane było dopiero po wyłączeniu trybu wirtualnego kursora. Mertama w C2Do-omie z tego nie korzystał, bo i do czego, a w OpenTTD (gra całkowicie kontrolowana myszą!) okazało się, ile jest to warte, wobec czego SDL-owy wskaźnik został usunięty.

SDL_ListModesWspomniana wcześniej problematyczna funkcja zachowywała się już co prawda zgodnie ze specyfikacją, jednak implemen-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?

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

Page 85: SDJ Extra 34 Biblia

84

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 85

jący bibliotekę o listę rozdzielczości wy-chodził poza przekazaną tablicę i odczyty-wał specjalnie ustawionego na 0 prywatne-go inta. Funkcja została zmieniona na po-prawną.

Wychodzenie z aplikacjiMertama błędnie założył, że naciśnięcie czerwonej słuchawki musi być równoważ-ne zabiciu aplikacji. SDL w takiej sytuacji wysyłał do aplikacji zdarzenie SDL_Quit i wyłączał dostęp do ekranu. Tymczasem OpenTTD po otrzymaniu tego zdarzenia wyświetla zapytanie czy na pewno chcesz wyjść z gry. Wyłączenie wyświetlania grafi-ki oczywiście uniemożliwiało użytkowni-kowi reakcję, a jedynym sposobem na wyłą-czenie aplikacji był restart telefonu. W mo-jej wersji jedyną reakcją biblioteki na naci-śnięcie klawisza czerwonej słuchawki jest wysłanie odpowiedniego zdarzenia.

Kolejne zadanie domowe dla czytelni-ków: proszę zakomentować obsługę SDL_Quit w programie z Listingu 1 i spróbować zamknąć program za pomocą przycisku za-mknięcia okna.

Interesującą ciekawostką jest fakt obsłu-gi czerwonej słuchawki w dwóch różnych miejscach. Okazuje się, że odpowiedni kod w metodzie AppUi::HandleCommandL jest wywoływany tylko za pierwszym naciśnię-ciem klawisza. Aby obsłużyć następne na-ciśnięcia, należy dodać obsługę do metody AppUi::HandleWsEventL.

Okno konsoli (?)Nikt nie wie, co to jest – przypuszczalnie również autor. W dokumentacji znajdu-je się o tym mętna wzmianka, jakichkol-wiek przykładów użycia brak. Wyleciało z hukiem.

Drawing OverlaysWśród dodanej funkcjonalności w porcie SDL-a znajdowała się możliwość naryso-wania czegoś warstwę wyżej niż rysowa-ła aplikacja; wirtualny kursor myszy był jednym z przypadków użycia (prawdopo-dobnie jedynym). Sam pomysł, żeby udo-stępniać funkcje, które są dostępne tylko na Symbianie, a do tego mogą być w pro-sty sposób zrealizowane w samej aplika-cji, jest trochę nie na miejscu, wobec cze-go w moim porcie Drawing Overlays zna-leźć nie można.

Skalowanie ekranuProblem został omówiony wcześniej, dla przypomnienia sobie, o co chodzi, wystar-czy spojrzeć na Rysunek 7. Argument pro-gramistów pracujących nad pozostałymi portami, jakoby użytkownicy woleli mieć rozciągnięty, zniekształcony obraz gry za-miast odwzorowania pikseli 1:1 z czarny-

Listing 3. Plik bld.inf

prj_mmpfiles

sdl.mmp

Listing 4. Plik sdl.mmp.

target sdl.exe

targettype exe

uid 0x100039ce 0xa0112233

vendorid 0

epocstacksize 0xA000

epocheapsize 0x100000 0x1000000

start resource sdl.rss

targetpath \private\10003a3f\import\apps

end

start resource sdl_loc.rss

targetpath \resource\apps

lang SC

end

option gcce -funit-at-a-time

source sdl.cpp

systeminclude \epoc32\include\stdapis

systeminclude \epoc32\include

systeminclude \epoc32\include\SDL

library apgrfx.lib

library apparc.lib

library avkon.lib

library bafl.lib

library bitgdi.lib

library cone.lib

library efsrv.lib

library eikcore.lib

library eiksrv.lib

library euser.lib

library fbscli.lib

library gdi.lib

library hal.lib

library libc.lib

library libm.lib

library libpthread.lib

library libstdcpp.lib

library libz.lib

library mediaclientaudiostream.lib

library remconcoreapi.lib

library remconinterfacebase.lib

library scdv.lib

library ws32.lib

library centralrepository.lib

staticlibrary sdl.lib

Page 86: SDJ Extra 34 Biblia

86

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 87

mi paskami na górze i dole, po prostu do mnie nie trafia. Co za tym idzie, skalować ekranu u mnie się nie da (a przynajmniej nie tak jak w reszcie portów, ale o tym tro-chę później).

Dostęp do pamięci ekranuPod Symbianem na ekranie można rysować na kilka różnych sposobów. Mertama wy-myślił sobie, że będzie wspierał wszystkie możliwe tryby dostępu, mimo że niektóre są niewydajne albo nawet określane przez dokumentację jako przestarzałe. Podejście takie argumentuje różnicami wydajności działania na różnych urządzeniach, co za-sadniczo żadnego programisty używającego SDL-a nie powinno interesować, od wybo-ru poprawnego trybu jest w końcu biblio-teka. Mertama bardzo również zachwala wykorzystanie BitGdi, które niby to jest najlepiej zaimplementowane, najbardziej pewne co do wsparcia w przyszłości, po-za tym wspierające systemowe efekty gra-ficzne i umożliwiające działanie w okienku. Wszystko byłoby dobrze, gdyby nie doku-mentacja jasno stwierdzająca, że to właśnie podejście jest przestarzałe i wolne, a zale-ca się używanie Direct Screen Access, czyli bezpośredniego dostępu do ekranu. Moja wersja biblioteki korzysta wyłącznie z tego trybu rysowania, a co za tym idzie jest tak szybka jak to tylko możliwe.

Orientacja ekranuPoprawna obsługa zmian orientacji ekra-nu zawsze była dla mnie jednym z klu-czowych elementów, które Simple Direct-Media Layer musi poprawnie obsługiwać. Problem wyjaśnić będzie najlepiej na przy-kładzie. Załóżmy, że posiadamy telefon z wbudowanym akcelerometrem, taki, któ-ry potrafi automatycznie zmienić orien-tację ekranu w zależności od tego, jak jest

trzymany, przykładowo N95 8GB. Na ta-kim telefonie uruchamiamy w portretowej orientacji ekranu ScummVM, wykorzystu-jący port SDL-a autorstwa Perssona. Obraz na ekranie będzie prawdopodobnie znie-kształcony; aby rozwiązać ten problem, musimy sięgnąć do dokumentacji, z któ-rej można dowiedzieć się, jaka kombina-cja klawiszy wyłącza skalowanie ekranu, ja-ka kombinacja obraca ekran itd. W efekcie powinniśmy uzyskać wynik zbliżony do te-go z Rysunku 8, gdzie możemy zaobserwo-wać obrócone menu. Naturalną reakcją jest obrócenie telefonu tak, by obraz był w pra-widłowym położeniu. Tutaj zaczynają się dziać śmieszne rzeczy: telefon zmienia orientację na krajobrazową (landscape), ale biblioteka, a co za tym idzie – aplikacja, te-go nie obsługuje. Opłakany efekt końcowy możemy zobaczyć na Rysunku 9. Popraw-nym zachowaniem byłoby oczywiście wy-muszenie przez aplikację stosowania orien-tacji landscape, niestety, zarówno Persson, jak i Mertama są dosyć uparci w tej spra-wie, twierdząc, że sposób obsługi orienta-cji zależy od telefonu, w większości przy-padków jest ona na poziomie systemu ope-racyjnego czysto programowa i dlatego le-piej stosować hacki w bibliotece polegające na trzymaniu się jednej sprzętowej orien-tacji i zmieniania sposobu rysowania na ekranie w zależności od ustawionej w bi-bliotece programowej orientacji. Podejście takie – jak widać na załączonym obrazku – zupełnie się nie sprawdza. Udostępniana przeze mnie wersja kodu nie tylko imple-mentuje obsługę orientacji sprzętowo, ale również zupełnie pozbawia programistę konieczności wiedzy o tym, że jakieś orien-tacje w ogóle istnieją. Biblioteka sama do-pasowuje orientację do zażądanej rozdziel-czości ekranu, a w przypadku stosowania wyświetlania pełnoekranowego potrafi za-

reagować na systemową zmianę orientacji przez wysłanie do aplikacji komunikatu o zmianie rozmiaru okna.

Należy tu wspomnieć, że w forku Mer-tamy pojawiło się jakiś czas temu wsparcie dla sprzętowych orientacji. Widocznie wy-starczająco długo narzekałem.

Wsparcie dla własnych blitterówKolejna rzecz, która została usunięta z powo-du przerzucania odpowiedzialności za funk-cjonalność, którą powinna implementować biblioteka na programistę aplikacji. Ktoś, kto wykorzystuje SDL nie powinien się intereso-wać szczegółami dostępu do ekranu.

Trzy biblioteki?SDL w wersji Mertamy dostarczany był w postaci trzech bibliotek: zasadniczego SDL-a, przykładowej implementacji pod-stawowej aplikacji symbianowej i małego kawałka kodu odpowiedzialnego za prze-kazanie opcji do biblioteki. Zgodnie z za-sadą mówiącą, że programista aplikacji nie powinien być odpowiedzialny za systemo-we sprawy, implementacja aplikacji została włączona do głównej biblioteki, przecho-dząc przy okazji małe odchudzenie. Na-tomiast kod przekazujący opcje został usu-nięty, razem z funkcjonalnością włączaną przez opcje (wirtualny kursor myszy, tryb dostępu do pamięci ekranu itd.).

Klawisze zmiany głośnościKwestia obsługi tych klawiszy w ogóle nie została poruszona w portach SDL-a. Jest to uciążliwe do tego stopnia, że w ScummVM głośność zmienia się, naciskając zieloną słuchawkę, co powoduje wejście do specjal-nego trybu, w którym klawisze góra/dół zmieniają głośność, po czym znowu naci-ska się zieloną słuchawkę, aby wrócić do normalnego stanu obsługi klawiszy. Do tego dochodzi jeszcze konieczność wcze-śniejszego ustawienia odpowiedniego try-bu skalowania ekranu, gdyż w przypadku stosowania nierozciągniętego obrazu kla-wisze góra/dół odpowiedzialne są za prze-suwanie ekranu.

Używanie tych klawiszy w kodzie Mer-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-by tego było mało, implementacja mecha-nizmu obserwatora, który pozwala na zmianę mapy klawiatury, posiada poważny błąd: po modyfikacji przez użytkownika biblioteka i tak nadpisuje ją swoją wersją.

Mój kod ma wbudowaną obsługę klawi-szy zmiany głośności. Programista musi tylko obsłużyć za pomocą standardowego sposobu obsługi klawiatury dwa dodatko-we kody klawiszy.

Ciekawsze projekty używające SDL na SymbianieOpenTTD – pomimo wielu naśladowców najlepsza do tej pory zabawa w budowanie impe-rium transportowego – http://www.tt-forums.net/viewtopic.php?t=35942;C2Doom – co tu dużo mówić, klasyka – http://koti.mbnet.fi/mertama/index_new.html;UAE4ALL – emulator Amigi – http://my-symbian.com/forum/viewtopic.php?p=360328#360328;DOSBox – emulator DOS-a – http://s60dosbox.sourceforge.net/;Rick Dangerous – kultowa platformówka – http://www.atopo.net/tool_rick.php;ScummVM – maszyna wirtualna pozwalająca uruchamiać stare gry przygodowe – http://scummvm.org/;OpenTyrian – jedna z lepszych strzelanek „w górę” – http://www.embeddev.se/agroot/tyrian_s60v3.sis.

Wsparcie dla emulatoraPrzede wszystkim: to, co przychodzi razem z SDK Symbiana i nazywa się „emulator”, to nie 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-celowym sprzętem, na którym potrafią się dziać dosyć dziwne rzeczy. Od debugowania na urządzeniu się nie ucieknie. A po co zawracać sobie głowę „emulatorem”, kiedy aplikację ko-rzystającą z SDL-a można uruchomić natywnie na pececie?

Page 87: SDJ Extra 34 Biblia

86

Programowanie Symbian OS

SDJ Extra 34 Biblia

Biblioteka SDL na Symbianie

www.sdjournal.org 87

Rozmiar bufora audioOdtwarzanie dźwięku na niskim poziomie opiera się na wypełnianiu bufora danych dźwiękowych, który jest następnie przeka-zywany niżej, do sprzętu. Wszystkie wyso-kopoziomowe metody odgrywania plików dźwiękowych mogą się starać ukrywać ten bufor, ale na wystarczająco niskim pozio-mie zawsze gdzieś się on pojawi. Mamy tu-taj interesujący problem: jak prawidłowo wybrać rozmiar bufora? Gdy bufor będzie mały (przykładowo 512 bajtów), słyszal-ne opóźnienie dźwięku będzie bardzo ma-łe – przy 16 bitowych próbkach i częstotli-wości 44 kHz w stereo system wymaga wy-pełnienia bufora co 3 ms. Niestety, jeżeli nie zdążymy z obsługą tego żądania, urzą-dzenie dźwiękowe nie będzie miało co od-twarzać – pojawi się chwila ciszy. Aby tego uniknąć, należy zwiększyć rozmiar bufora, co da aplikacji więcej czasu na reakcję, ale jednocześnie wiąże się ze wzrostem słyszal-nego opóźnienia dźwięku. Urządzenie au-dio ma, przykładowo, pół sekundy danych, jeżeli będziemy chcieli teraz odtworzyć ja-kiś dźwięk, usłyszymy go dopiero gdy te da-ne się skończą i ponownie będziemy musie-li wypełnić bufor.

Rozważania te są ważne dla zrozumienia problemu istniejącego w bibliotece Mertamy. Można tam było podać rozmiar bufora pro-gramowego, ale bufor sprzętowy zawsze miał 256 bajtów! Pięknie łączyło to wszystkie pro-blemy obu alternatyw, nie dając zalet żadnej z nich. Po uproszczeniu nieźle przekombino-wanego kawałka kodu moja wersja po pierw-sze zachowuje się zgodnie z oczekiwaniami, po drugie działa dużo bardziej wydajnie.

Persson, prawdopodobnie dochodząc do podobnych wniosków, w swoim porcie przepisał system dźwiękowy od zera.

Parametry aplikacjiMertama umożliwiał wprowadzenie parame-trów aplikacji przed jej uruchomieniem, wy-świetlając symbianowy dialog. Podczas two-rzenia aplikacji można to było obejść, umiesz-czając w konkretnym miejscu plik, jednak by-ło to dosyć uciążliwe dla programisty. Pozo-stawienie możliwości wprowadzenia dodat-kowych opcji wprowadzało natomiast niektó-rych użytkowników w zakłopotanie. Coś mi wyskoczyło, co mam wpisać? – pytali. Zamie-szanie wprowadzane przez tę funkcjonalność połączone ze stosunkowo dużym rozmiarem kodu odpowiedzialnym za obsługę w końcu zaważyło nad jej usunięciem.

Trzeszczący dźwiękSymbianowy SDL miał dalsze problemy z od-twarzaniem dźwięków. Wyczulone ucho by-ło w stanie wychwycić przerwy w odtwarza-nym dźwięku. Analiza w edytorze potwier-dziła przypuszczenia, fragment strumienia

audio z zaznaczonymi przerwami można zo-baczyć na Rysunku 10. Zastąpienie dziwacz-nie obliczanych opóźnień występujących w kodzie obsługującym bufory audio przez pro-sty mechanizm naśladujący muteksy rozwią-zało problem.

Nagranie dźwięku przed modyfikacjami: http://team.pld-linux.org/%7Ewolf/symbian/audio/old.ogg. Nagranie po modyfikacjach: http://team.pld-linux.org/%7Ewolf/symbian/audio/new.ogg.

Wyłączanie dźwiękuPrawidłowa obsługa wyłączania dźwięku na telefonach komórkowych jest bardzo ważna, nie chcemy w końcu, żeby w cza-sie gdy użytkownik prowadzi rozmowę te-lefoniczną, grała w tle muzyka. Realizacja w bibliotece Mertamy była bardziej szyb-kim hackiem niż zaplanowanym działa-niem – za sterowanie urządzeniem dźwię-kowym odpowiedzialny był kod wyświetla-jący grafikę, poza tym nie można było wy-

Listing 5. Plik sdl.pkg

&EN

; standard SIS file header

#{"SDL"},(0xA0112233),1,0,0

;Localised Vendor name

%{"Vendor-EN"}

;Unique Vendor name

:"Vendor"

[0x101F7961], 0, 0, 0, {"S60v3"}

[0x1028315F], 0, 0, 0, {"S60v5"}

;Files to install

"\epoc32\release\gcce\urel\sdl.exe" - "!:\sys\bin\sdl.exe"

"\epoc32\data\z\private\10003a3f\import\apps\sdl.rsc" - "!:\private\10003a3f\

import\apps\sdl.rsc"

"\epoc32\data\z\resource\apps\sdl_loc.rsc" - "!:\resource\apps\sdl_loc.rsc"

Listing 6. Plik sdl.rss

#include <appinfo.rh>

UID2 KUidAppRegistrationResourceFile

UID3 0xA0112233

RESOURCE APP_REGISTRATION_INFO

{

app_file="sdl";

localisable_resource_file="\\resource\\apps\\sdl_loc";

}

Listing 7. Plik sdl_loc.rss

#include <appinfo.rh>

RESOURCE LOCALISABLE_APP_INFO

{

short_caption = "SDL Test";

caption_and_icon =

{

CAPTION_AND_ICON_INFO

{

caption = "SDL Test";

number_of_icons = 0;

}

};

}

Page 88: SDJ Extra 34 Biblia

88

Programowanie Symbian OS

SDJ Extra 34 Biblia

łączyć odtwarzania dźwięku z pozostawio-nym miksowaniem. Istnieją aplikacje, w których synchronizacja czasowa powiąza-na jest z odtwarzaniem dźwięku, nie mo-żemy sobie więc pozwolić na zupełne wy-łączenie obsługi audio, nawet gdy nic nie gra z głośników.

W mojej wersji biblioteki obsługa dźwię-ku jest całkowicie wyłączona, gdy aplikacja znajduje się w tle. Gdy włączony jest pro-fil milczący (przypadek w ogóle nie ob-sługiwany w pozostałych wariantach bi-blioteki), wyjście audio jest wyłączone, ale dźwięk jest miksowany.

Zużycie energiiPomiary obciążenia baterii wykonane apli-kacją Nokia Energy Profiler nie nastraja-ły pozytywnie. Aplikacja podczas działania potrzebowała 1,07W, co jest akceptowalne, w końcu interesuje nas jak największa wy-dajność gry, gdy w nią gramy. Dużo gorzej przedstawiały się wyniki aplikacji chodzą-cej w tle, która zużywała aż 0,51W, co silnie kontrastowało z przypadkiem, gdy aplikacja nie była włączona – wtedy wykorzystanie energii plasowało się na poziomie 0,11W, co widać na Rysunku 11. Tylko w moim porcie zużycie energii przez aplikację działającą w tle zostało obniżone do 0,13W.

Format koloru pikselaMój port SDL-a wewnętrznie obsługu-je tylko format 565, podczas gdy pozosta-łe warianty starają się obsługiwać możliwie wszystkie. Różnica jest czysto architektu-ralna, dla programisty wykorzystującego bi-bliotekę nic się nie zmienia. W dalszym cią-gu może używać koloru ośmio- czy 32-bito-wego, jedynie wewnętrzny bufor jest szesna-stobitowy, co może co prawda spowodować nieznaczne obniżenie jakości finalnego ob-razu, jednak znacznie upraszcza kod i umoż-liwia bardzo szybkie obliczenie średnie-go koloru z czterech pikseli. Ta funkcjonal-ność jest wykorzystywana do obsługi trybów graficznych o rozdzielczości większej niż fi-zyczna rozdzielczość wyświetlacza telefonu – na ekranie wielkości 320x240 można wy-

świetlić obraz wielkości 640x480, co więcej, jest to obraz poprawnie zmniejszony, bez uciekania się do sztuczek takich jak rysowa-nie co drugiego piksela.

Powyższa lista problemów jest dość po-kaźna, nie jest to bynajmniej przechwala-nie się, tylko próba uwidocznienia najważ-niejszych mankamentów, na które moż-na się natknąć podczas używania bibliote-ki Simple DirectMedia Layer pod Symbia-nem. Największym dowcipem jest promo-wane przez Mertamę hasełko S60 program-ming without Symbian!, które w zderzeniu z rzeczywistością okazuje się tak napraw-dę stekiem bzdur. Dość wspomnieć o stric-te symbianowej klasie CSDL i wszystkich usuniętych przeze mnie dodatkach.

Trochę praktykiMożna powiedzieć: no dobra, a jak tego użyć? Przede wszystkim musimy mieć za-instalowane symbianowe SDK. Szczegóło-we instrukcje dotyczące instalacji można znaleźć w SDJ 06/2008, tutaj nie będziemy się wgłębiać w temat. Zakładam też, że czy-telnik ma pojęcie o budowaniu programów na Symbiana przynajmniej na poziomie po-mocy dostarczanej w ramach SDK.

Bibliotekę SDL w wersji obsługującej S60 można pobrać na dwa różne sposoby. Jeże-li ktoś używa systemu kontroli wersji Git, wystarczy, że sklonuje adres git://repo.or.cz/SDL.s60v3.git. Alternatywą jest pobranie paczki z najnowszą wersją źródeł spod adre-su http://repo.or.cz/w/SDL.s60v3.git?a=snap-shot;sf=tgz. Tutaj ważna uwaga: w obu przy-padkach nie ma żadnej gwarancji stabilności kodu, zazwyczaj jednak sprawdzam zmiany przed wysłaniem ich na serwer.

Aby skompilować bibliotekę, wchodzi-my do katalogu symbian:

cd symbian

Następnie wydajemy polecenia urucha-miające proces budowania:

bldmake bldfiles

abld build gcce urel

Wynikowa biblioteka statyczna i wymagane nagłówki zostaną automatycznie umieszczo-ne w odpowiednich katalogach SDK.

Zbudowanie naszej przykładowej aplikacji z Listingu 2 nie wymaga oczywiście żadnych zmian w kodzie, ale niestety proces nie jest prosty i przyjemny z powodu konieczności stworzenia plików odpowiedzialnych za reje-strację aplikacji w menu telefonu. W katalo-gu z programem tworzymy pliki z Listingów 3, 4, 5, 6, 7. Stosunkowo pokaźna lista biblio-tek w pliku sdl.mmp wynika z braku możli-wości zlinkowania statycznej biblioteki z pli-kami przez nią wymaganymi. Ostatnim kro-kiem jest wydanie poleceń:

bldmake bldfiles

abld build gcce urel

makesis -d$EPOCROOT sdl.pkg

signsis sdl.sis sdl.sisx

Uwaga: na Windowsach może być wymaga-na drobna korekta składni polecenia signsis (dodanie certyfikatów, można je wcześniej wygenerować komendą makekeys).

Wygenerowany plik sdl.sisx wrzucamy na telefon, instalujemy i cieszymy się dzia-łającym programem.

Słowo na koniecZapoznaliśmy się z dostępnymi portami bi-blioteki SDL na Symbiana, poznaliśmy ich wady i zalety. Nie pozostaje nic innego, jak brać się za przenoszenie istniejących gier i aplikacji na telefony. Wyrażam tylko nadzie-ję, że przy wyborze używanej wersji biblio-teki programiści będą się kierować wygodą użytkownika, czyli również moją.

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;

BARTOSZ TAUDULBartosz Taudul zajmuje się rozwojem jednego z forków biblioteki SDL na S60. Pracuje również ja-ko programista w firmie Gamelion wchodzącej w skład Grupy BLStream; częścią jego obowiązków jest optymalizacja i rozwój niskopoziomowej bi-blioteki zapewniającej jednolity dostęp do sprzętu z poziomu aplikacji na różnych platformach.Kontakt z autorem: [email protected]

Page 89: SDJ Extra 34 Biblia
Page 90: SDJ Extra 34 Biblia

90

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 91

Naturalną konsekwencją ukazania się na rynku nowego systemu ope-racyjnego Android była koniecz-

ność zapewnienia mu odpowiednich kana-łów dystrybucji oprogramowania. Tak jak wiele osób dopatruje się analogii pomiędzy platformami Android oraz iPhone, trudno było nie oczekiwać udostępnienia przez Go-ogle systemu sprzedaży podobnego do iPho-ne App Store. Tak się rzeczywiście stało, kie-dy to oficjalnie ogłoszone zostało uruchomie-nie Android Market.

Z perspektywy użytkownika telefonu An-droid Market jest programem, po którego uruchomieniu możliwe jest odnalezienie wybranej aplikacji, jej zakup, pobranie i in-stalacja.

Dla producentów oprogramowania jest on jednak zdecydowanie czymś więcej – inter-fejsem, za pośrednictwem którego dotrzeć można do wielkiej liczby odbiorców. Zgod-nie z zapowiedziami twórców, Android Mar-ket ma różnić się od podobnych rozwiązań ła-twością procesu publikacji oraz przejrzysto-ścią działania.

Od ukazania się na rynku pierwszego urzą-dzenia HTC G1, wyposażonego w system Android, minęło niewiele ponad pół roku. Choć z perspektywy polskiego użytkowni-ka platforma ta może wydawać się niszową, należy pamiętać o tym, że do końca bieżące-go roku planowana jest sprzedaż aż 18 zupeł-nie nowych modeli urządzeń Android! Do gry wkraczają kolejni operatorzy telefonii ko-mórkowej, obejmujący swym zasięgiem co-raz nowsze rynki na całym świecie. Obecność systemu w świadomości użytkowników na-biera coraz silniejszych akcentów i systema-tycznie staje się on coraz bardziej liczącym się graczem na rynku platform mobilnych. Faktu tego nie mogą zignorować producen-ci i dystrybutorzy oprogramowania. Znajo-mość Android Market staje się zatem punk-tem wyjścia do zaznaczenia swojej obecno-ści na rynku.

W chwili powstawania tego artykułu An-droid Market zawiera prawie 6000 różnych aplikacji, z czego około tysiąc stanowią gry. Wraz z przewidywanym rozwojem rynku liczba produktów wzrastać będzie lawino-wo. Wiąże się to nie tylko ze wzrostem listy potencjalnych użytkowników, ale i krysta-lizowaniem się grona dużych graczy wśród producentów oprogramowania dla systemu Android. W początkowym okresie – przy-najmniej teoretycznie – szanse są wyrów-

nane dla wszystkich uczestników rynku, ale taka sytuacja z pewnością nie będzie trwać wiecznie.

Android Market z perspektywy użytkownikaAplikacja Android Market dostępna jest na li-ście programów każdej standardowej dystry-bucji systemu, zainstalowanej na urządzeniu z logo Google. Można ją uruchomić poprzez wybranie charakterystycznej ikony z logo An-droid oraz podpisem Market. Po uruchomie-niu programu górną część okna zajmuje lista ikon promowanych aplikacji oraz ich nazw. Niżej do dyspozycji użytkowników są nastę-pujące przyciski:

Android Market bliżej dewelopera

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ł?

Dowiesz się:• Jak poruszać się w gąszczu aplikacji jako

użytkownik telefonu Google;• Jak opublikować swoją aplikację na Android

Market;• Jak trafić do największej liczby odbiorców.

Powinieneś wiedzieć:• Jakie są podstawowe założenia budowy i

działania aplikacji Android;• Jakie są zasady funkcjonowania finansowych

transakcji online.

Poziom trudności

Stworzenie aplikacji to dopiero początek. Dowiedz się, jak ją sprzedać!

Rysunek 1. Główny ekran klienta Android Market

Page 91: SDJ Extra 34 Biblia

90

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 91

• Applications;• Games;• Search;• My Downloads.

Wybranie pierwszych dwóch pozycji skut-kuje wyświetleniem pełnej listy podkate-gorii oprogramowania, a także opcji All ap-plications, za pomocą której możliwy jest dostęp do całości listy w dziale, bez po-działu na kategorie. Więcej na temat kate-gorii aplikacji na Android Market dowiesz się w dalszej części artykułu, poświęco-nej publikacji programów. Przycisk Search umożliwia wprowadzenie nazwy poszu-kiwanego produktu, natomiast My Down-loads uruchamia listę wszystkich progra-mów zainstalowanych dotychczas na da-nym urządzeniu. Jest to bardzo przydatne narzędzie, ponieważ przy nazwie każdego z produktów widoczny jest również jego bieżący status. Najczęściej zaobserwujemy jeden z dwóch stanów: Installed bądź też Update Available, co oznacza, że na serwe-rze pojawiała się nowsza wersja programu i możliwe jest jej ściągnięcie.

Po otwarciu dowolnej kategorii w trybie przeglądania aplikacji mamy możliwość wy-świetlenia dwóch widoków:

• By popularity – aplikacje posortowane są według popularności: od największej do najmniejszej liczby ściągnięć;

• By date – na szczycie listy wyświetlane są najnowsze produkty.

W dodatkowych opcjach (po wciśnięciu przycisku Menu) mamy również możli-wość zmiany widoku poprzez wyświetle-nie jedynie płatnych lub darmowych pro-gramów, bądź też obu tych grup naraz. Li-sta płatnych programów dostępna jest je-dynie dla użytkowników w krajach, w któ-rych Android Market umożliwia sprze-daż. Użytkownicy z Polski w poszukiwa-niu płatnych programów muszą jak na ra-zie skorzystać z konkurencyjnych plat-form dystrybucji.

Po wybraniu określonej aplikacji w któ-rymkolwiek z trybów (przeglądanie we-dług kategorii, zaznaczenie jednej z promo-wanych aplikacji albo bezpośrednie wyszu-

kanie) wyświetlony zostaje jej krótki opis oraz informacje dodatkowe, takie jak licz-ba i średnia wartość ocen, numer wersji, rozmiar oraz liczba ściągnięć, przy czym nie jest podawana dokładna wartość, a je-dynie przedział liczbowy dający ogólne wy-obrażenie o popularności danego produk-tu. Dla najbardziej rozchwytywanych apli-kacji przewidziana jest etykieta >250,000 downloads.

Poniżej opisu widoczne są najnowsze ko-mentarze użytkowników, możliwe jest też wyświetlenie ich pełnej listy. Końcową sek-cję ekranu opisowego stanowią informa-cje o twórcy, odnośnik do pełnej listy pro-duktów danego dewelopera, adres strony WWW oraz przycisk umożliwiający na-tychmiastowe wysłanie do niego wiadomo-ści e-mail. Dodano również opcję Flag as in-appropriate, po której wybraniu możemy zgłosić naruszenie przez dewelopera zasad dystrybucji.

W dolnej części ekranu dostępny jest przycisk Install. Po jego wciśnięciu ukaże się lista uprawnień, z których korzysta apli-kacja. Przykładowo, dla programu wyko-

Kamienie milowe rozwoju Android Market

• 23.10.2008 – oficjalne uruchomienie Android Market z około 50 darmowymi aplikacjami do zainstalowania;• 28.11.2008 – po miesiącu działalności przedstawiono pierwsze statystyki dla rynku. Najpopularniejszą aplikacją okazała się gra Pac-Man fir-

my Namco, z ponad 250.000 instalacjami;• 19.01.2009 – zapowiedź uruchomienia darmowej wersji Android Market w krajach europejskich, w tym w Polsce, co w niedługim czasie sta-

je się faktem;• Luty 2009 – oficjalne zakończenie okresu Beta dla platformy. Nowa jej wersja wspiera aktualizację wersji oprogramowania;• 13.02.2009 – uruchomiono sprzedaż aplikacji na rynkach amerykańskim i brytyjskim;• Marzec 2009 – operator T-Mobile ogłosił kolejne statystyki, według których przeciętny użytkownik telefonu G1 zainstalował 40 aplikacji;• 06.05.2009 – rozwinięcie listy krajów, gdzie dostępny jest darmowy Android Market, oraz dodanie wsparcia dla nowych języków, w tym ję-

zyka polskiego. Wprowadzono również konieczność określania przez programistów wspieranej przez aplikacje wersji SDK.

Rysunek 2. Widok listy aplikacji kategorii Entertainment posortowanych według daty

Rysunek 3. Prezentacja listy wyników wyszukiwania

Rysunek 4. Informacje szczegółowe dla wybranej aplikacji

Page 92: SDJ Extra 34 Biblia

92

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 93

rzystującego ciągłe połączenie z siecią wy-świetlony zostanie komunikat: This applica-tion has access to the following: Network com-munication – full Internet access. Po zaak-ceptowaniu uprawnień wymaganych przez program uruchomione jest jego pobiera-nie, którego postęp widoczny jest w syste-mowym pasku notyfikacji. Gdy zapisywa-nie zostanie ukończone, następuje powrót do listy aplikacji, gdzie obok nazwy pobra-nego programu ustawiony zostaje status In-stalled. Po ponownym wybraniu zainstalo-wanej już aplikacji możliwa jest już jej oce-na, poprzez nadanie odpowiedniej liczby gwiazdek.

Interfejs użytkownika Android Market jest w zasadzie tak prosty jak to tylko moż-liwe dla aplikacji, której zadaniem jest wy-szukiwanie i instalacja oprogramowania. Według wielu opinii, z prostotą związa-ne są też znaczne ograniczenia w funkcjo-nalności klienta. Z pewnością bardzo od-czuwalny jest brak możliwości publikacji przez deweloperów zrzutów ekranu dzia-łających aplikacji, co ułatwiałoby podjęcie decyzji użytkownikom, zwłaszcza przed pobraniem płatnych programów. Na pew-no nie zaszkodziłoby poprawienie możli-wości systemu w zakresie prezentacji listy aplikacji, choćby przez rozwinięcie kryte-riów ich filtrowania. Należy jednak pamię-tać, że Android Market, jak i cała platfor-ma, jest systemem bardzo młodym i podle-gającym ciągłemu procesowi rozwoju. Mo-żemy zatem liczyć na pojawianie się z cza-sem kolejnych poprawek mających na celu usprawnienie nawigacji. Zanim to nastąpi, możemy skorzystać na przykład z interne-towych klientów Android Market, pełnią-cych funkcję przeglądarek aplikacji.

Rejestracja konta deweloperaProces produkcji naszej aplikacji dobiegł wła-śnie końca. Programista, dumny ze swoje-go dzieła, ma teraz chwilę zasłużonego od-poczynku (tak, tak, po prostu wpada w wir pracy nad kolejnym programem). Jeżeli ce-lem przyświecającym stworzeniu aplikacji było udostępnienie jej szerszej publiczności, nadszedł czas na publikację i sprzedaż pro-gramu.

Publikacja aplikacji za pośrednictwem Android Market nie jest zadaniem trud-nym, w wielu przypadkach zajmuje się tym sam programista. Spora część czyn-ności związanych z udostępnieniem apli-kacji wykonywana jest tylko raz i wynika bezpośrednio z rejestracji konta użytkow-nika. Publikacja każdej kolejnej aplikacji staje się procesem trywialnym, niemal au-tomatycznym.

Aby mieć możliwość publikacji programu na Android Market, konieczna jest najpierw rejestracja konta użytkownika. W uproszcze-niu, zgodnie z instrukcją Google, składają się na nią następujące kroki:

• rejestracja konta użytkownika;• uiszczenie opłaty rejestracyjnej;• wyrażenie zgody na warunki umowy

Android Market Developer Distribution Agreement.

Punktem wyjścia jest oczywiście oficjal-na strona serwisu http://www.android.com/market/. Uwagę zwraca przede wszystkim lista reklamowanych aplikacji, ale to nie one są naszym głównym punktem zain-teresowania. Pod niepozornym linkiem Learn more kryje się możliwość publika-

cji programu na rynku Android Market. Wejście do tej części serwisu wymaga już zalogowania. W tym celu potrzebne jest stworzenie konta użytkownika Google. Wbrew pozorom nie jest ono jednoznacz-ne ze stworzeniem konta poczty Gmail, choć taki wybór jest najwygodniejszy i po-lecany przez autora artykułu.

Po pierwszym zalogowaniu się do dewelo-perskiej części serwisu Android Market, nale-ży wprowadzić podstawowe informacje o na-szym profilu. Są to:

• nazwa dewelopera – informację, którą tu wprowadzimy, użytkownicy końco-wi zobaczą pod nazwą aplikacji widocz-ną na liście programów. Powinna to być dobrze przemyślana nazwa, ponieważ nie ma możliwości cofnięcia wybranych kroków rejestracji. Dane możemy zmie-nić dopiero w trybie edycji działającego konta;

• kontaktowy adres e-mail – standardo-wo wyświetlony zostaje adres, za po-mocą którego logujemy się do nasze-go konta, możliwe jest podanie innego adresu, ale nie jest to polecane rozwią-zanie;

• adres strony internetowej dewelopera;• numer telefonu kontaktowego.

Podczas rejestracji możemy również wyra-zić zgodę na otrzymywanie informacji do-tyczących produkcji i publikacji programów na platformę Android. Jeżeli chcemy być na bieżąco z rozwojem systemu, zdecydowanie powinniśmy zaznaczyć tę opcję. Zatwier-dzając wszystkie powyższe informacje, na-leży pamiętać, że proces rejestracji nie daje 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-ży adres e-mail, standardowo polecane jest wprowadzenie tego samego adresu, którego użyliśmy w początkowej fazie rejestracji do

Rysunek 6. Informacja o wszystkich zainstalowanych aplikacjach przedstawiona jest w dziale My downloads

Rysunek 5. Lista wymaganych uprawnień wyświetlona przed instalacją

Page 93: SDJ Extra 34 Biblia

92

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 93

kont Google oraz Android Market. Tym ra-zem również wymagana jest od nas akcepta-cja umowy (wszak jest to niezależna usługa Google). Wkrótce po zatwierdzeniu wszyst-kich wprowadzonych przez nas danych, na podany adres e-mail otrzymamy wiadomość z potwierdzeniem dokonania zamówienia na produkt o nazwie Android – Developer Re-gistration Fee.

Do finalizacji rejestracji konta deweloper-skiego potrzebna jest jeszcze akceptacja umo-wy Android Market Developer Distribution Agreement, której tekst ukaże się przed pu-blikacją pierwszego programu. Treść umowy można też przeczytać w dowolnej chwili po-przez kliknięcie odpowiedniego odnośnika w głównym panelu konta.

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-cje te wyświetlane są w następujących ko-lumnach:

• nazwa, numer wersji oraz kategoria, do której należy aplikacja;

• średnia ocena aplikacji wśród użytkow-ników (wyrażona w 5-gwiazdkowej ska-li) i liczba głosów;

• całkowita liczba ściągnięć i instalacji;• cena, w jakiej oferujemy program, bądź

informacja FREE, jeśli jest to aplikacja darmowa;

• informacja o tym, czy aplikacja została upubliczniona.

Początkowo lista aplikacji jest oczywiście pu-sta. Do wysłania programu na serwer zachę-ca przycisk Upload Application. Zanim jed-nak przejdziemy do wysłania aplikacji, nale-ży ją podpisać.

Zgodnie z założeniami systemu Android, wszystkie instalowane (a zatem i publikowa-ne) aplikacje muszą zostać podpisane za po-

mocą certyfikatu, którego klucz prywatny znajduje się w rękach twórcy. Odstępstwa od tej reguły nie stanowią aplikacje uruchamia-ne na urządzeniu w procesie produkcji.

W tym przypadku podpisywane są one automatycznie w trybie debug, który jed-nocześnie uniemożliwia publikację na An-droid Market.

Dozwolone, a nawet powszechnie stoso-wane jest samodzielne generowanie klucza po stronie deweloperów, jako że nadrzęd-nym celem jego zastosowania jest zapewnie-nie jednoznacznej identyfikacji twórcy opro-gramowania.

Ze względu na fakt, że każdy certyfikat posiada swój termin ważności, informa-cja ta poddawana jest sprawdzeniu tylko

w chwili instalacji programu na urządze-niu docelowym.

Jeżeli termin ważności upłynie już po in-stalacji, fakt ten nie wpływa na poprawne funkcjonowanie programu.

Zalecane jest stosowanie tego samego cer-tyfikatu dla wszystkich aplikacji publikowa-nych przez danego twórcę. Powodów takiego podejścia jest kilka:

• możliwość łatwej aktualizacji oprogra-mowania przez użytkownika końcowe-go w przypadku potwierdzenia tożsa-mości twórcy;

• możliwość uruchomienia kilku aplika-cji podpisanych tym samym certyfika-tem w ramach jednego procesu (w takiej

Rysunek 7. Wprowadzanie podstawowych informacji o deweloperze podczas rejestracji konta. Ekran edycji danych konta ma praktycznie tę samą zawartość

Rysunek 8. Główny panel zarządzania kontem dewelopera. Uwaga: widoczna aplikacja Global Factbook European Edition jest produktem firmy Gamelion Studios, który został opublikowany w wersji darmowej już w początkowym okresie działania Android Market

Page 94: SDJ Extra 34 Biblia

94

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 95

sytuacji kilka współdziałających ze sobą aplikacji możemy traktować jako jeden program);

• współdzielenie kodu i danych przez apli-kacje podpisane określonymi certyfika-tami.

Dział Dev Guide strony http://developer.android.com/ zawiera bardzo dobrze przygotowaną sekcję wyczerpują-cą tematykę podpisywania aplikacji. Dowie-my się z niej między innymi, że do wygene-rowania klucza i podpisania aplikacji wyko-rzystać możemy darmowe narzędzia Keytool oraz Jarsigner.

Kolejną rzeczą, o której należy pamiętać przed publikacją aplikacji, jest jej poprawne wersjonowanie. Jak się okazuje, termin ten dotyczy w rzeczywistości dwóch aspektów:

• wersji aplikacji;• wersji Android SDK, którą wspiera na-

sza aplikacja.

Obie informacje określa się w pliku Android-Manifest.xml. Udzielenie pierwszej z nich nie jest wymagane przez system, chociaż każdy zdaje sobie sprawę z wagi nadawania nume-rów wersji programom i trudno jest wyobra-zić sobie pominięcie tej informacji. Koniecz-ne jest natomiast sprecyzowanie drugiego z wymienionych parametrów w celu zapew-nienia kompatybilności oprogramowania z docelowymi urządzeniami, na których zo-stanie zainstalowane. Do pliku manifest na-leży dodać zatem następującą linię:

<uses-sdk android:minSdkVersion=”1”></

uses-sdk>

Możliwe przy tym jest nadanie atrybutowi android:minSdkVersion trzech wartości, w zależności od wspieranego SDK:

• "1" dla SDK 1.0;• "2" dla SDK 1.1;• "3" dla SDK 1.5.

Jako programista należy być świadomym zagadnienia wstecznej kompatybilności oprogramowania. Zalecane jest więc za-pewnienie działania aplikacji zbudowanej na starszym SDK, na nowszych urządze-niach.

Powyżej wymienionych zostało kilka naj-ważniejszych zagadnień, z którymi trzeba się liczyć przed publikacją programu na Andro-id Market. Po bardziej szczegółowe informa-cje odsyłam na oficjalną stronę dla dewelope-rów aplikacji Android, przyjrzyjmy się nato-miast właściwemu procesowi publikacji pro-gramu.

Publikacja aplikacji na Android MarketNajważniejszą informacją, jakiej musimy udzielić po wciśnięciu przycisku Upload Application jest oczywiście lokalizacja pli-ku apk, który chcemy umieścić na serwe-rze. Po jego wskazaniu (i wysłaniu) zosta-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"></

uses-permission>

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ń-ski) dostępne są również języki: polski, ho-

Rysunek 9. W ramach jednego ekranu realizowane jest wysłanie aplikacji na serwer, a także wprowadzenie jej opisu i parametrów publikacji

Rysunek 10. Wybór lokacji, w których program będzie dostępny dla odbiorców

Page 95: SDJ Extra 34 Biblia

94

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 95

lenderski, czeski, portugalski, tajwański i japoński.

W każdym z wybranych języków nale-ży określić nazwę aplikacji widoczną przez użytkowników końcowych oraz jej opis. Niestety, liczba znaków dla obu pól ograni-czona została dość znacznie – maksymalna długość nazwy aplikacji wynosi 30 znaków, a opis nie może być dłuższy niż 325 zna-ków. Na załączonej ilustracji widać, jakie wyzwanie stanowić może taka długość tek-stu. Poza opisem czeka nas określenie typu i kategorii aplikacji, informacji, na podsta-wie których nasz program zostanie zakwa-lifikowany do odpowiedniej grupy tema-tycznej.

Jak widać na ilustracji, kolejną ważną, o ile nie najważniejszą informacją towarzyszą-cą naszej aplikacji jest jej cena. Podkreślić na-leży, że waluta, w której możemy wprowa-dzić cenę naszej aplikacji, odpowiada walucie określonej w szczegółowych danych naszego konta Google Checkout. Stworzone dla po-trzeb artykułu konto zostało zarejestrowane w Wielkiej Brytanii, stąd kod dostępnej walu-ty to GBP. Cena dla użytkowników Android Market spoza wysp brytyjskich zostanie prze-liczona na odpowiednią jednostkę zgodnie z bieżącym kursem walut. Niewątpliwie jest to dowodem niedojrzałości systemu, zwłasz-cza z perspektywy deweloperów, którzy jak na razie muszą zdać się jedynie na zgrubną kalkulację zysków generowanych przez apli-kację sprzedawaną jednocześnie na różnych rynkach.

Pozostałe parametry publikacji programu związane są z jego dostępnością na określo-nych rynkach. Jeżeli nie interesuje nas dotar-cie do wszystkich użytkowników systemu

na świecie (służy do tego opcja All Current and Future Countries), możemy wybrać do-wolne kraje z dostępnej listy. Należy jednak pamiętać o tym, że zawartość listy zmienia się dość dynamicznie wraz ze zwiększaniem się zasięgu Android Market na świecie.

Na tym kończy się zestaw informacji wy-maganych przy wysyłaniu aplikacji na ser-wer. Możliwa jest jeszcze modyfikacja da-nych kontaktowych, o ile dla danej aplika-cji różnią się od informacji podanych przy tworzeniu konta. Po zaakceptowaniu wa-runków umowy dystrybucyjnej możemy wcisnąć przycisk Publish bądź skorzystać z opcji Save.

Niezależnie od wybranej opcji, nasz pro-gram ukaże się na liście aplikacji widocznej w panelu głównym konta dewelopera. Je-żeli jednak skorzystaliśmy z przycisku Sa-ve, obok nazwy aplikacji widoczny będzie przypis Unpublished, co oznacza, że nie jest ona jeszcze widziana przez użytkowników końcowych. Status ten można w dowolnej chwili zmienić.

Po ponownym wybraniu aplikacji z listy widocznej w panelu głównym możliwe jest skorzystanie z jednej z dodatkowych opcji:

• upload upgrade – udostępnienie aktuali-zacji programu – użytkownicy zauważą powiadomienie Update available w dzia-le My Downloads;

• unpublish – wyłączenie widoczności aplikacji na rynku.

Co powinieneś wiedzieć o sprzedaży?Jeżeli poważnie podchodzimy do zagadnie-nia sprzedaży aplikacji za pośrednictwem

Android Market, powinniśmy koniecznie zapoznać się z kilkoma zasadami związa-nymi z finansowymi aspektami tego kana-łu dystrybucji. Podstawową dla sprzedają-cego informacją będzie na pewno tzw. pay-out, czyli rzeczywisty odsetek ceny aplika-cji, jaki w wyniku sprzedaży trafia do dewe-lopera. Odsetek ten jest stały i wynosi 70%, podobnie jak w przypadku konkurencyjne-go rynku dla urządzeń iPhone – App Store. Informację tę należy brać pod uwagę w mo-mencie określania ceny naszego programu. Pozostała część wpływu ze sprzedaży trafia prawdopodobnie do operatorów sieci, choć informacja ta nie została oficjalnie potwier-dzona przez Google. Cena z kolei zmieścić się musi w narzuconych przez system wi-dełkach. Jako że na razie dostępne są dwie waluty dla przetwarzania transakcji, okre-ślono następujące limity cenowe:

• 0,99 – 200,00 USD;• 0,50 – 100,00 GBP.

Zabezpieczono się w ten sposób przed usta-laniem przez sprzedających absurdalnych cen aplikacji, co miało już miejsce na wymie-nionym App Store (jak w znanym przypad-ku zupełnie bezużytecznej aplikacji I am rich za 999,99USD). Z kolei minimum cenowe opiera się na założeniu, że niższa cena progra-mu nie ma uzasadnienia finansowego, i lepiej przeznaczyć go do dystrybucji bezpłatnej.

Mechanizm przetwarzania płatności jest stosunkowo prosty i opiera się w całości na systemie Google Checkout. Wszystkim transakcjom towarzyszą odpowiednie ko-munikaty e-mail wysyłane do zaintereso-wanych stron. Dzieje się tak w przypadku

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

• W czasie gdy przygotowywany jest ten numer magazynu, bezpłatne aplikacje dostępne są dla większości użytkowników z Unii Europej-skiej, w tym z Polski, a także z Norwegii, Szwajcarii, USA, Australii, Kanady, Hong Kongu, Japonii, Singapuru i Tajwanu.

• Lista krajów, w których możliwa jest dystrybucja płatnych aplikacji ograniczona jest obecnie do USA, Wielkiej Brytanii, Australii, Austrii, Włoch, Francji, Niemiec Hiszpanii i Holandii.

• Lista krajów, w których możliwa jest rejestracja deweloperów chcących sprzedawać swoje aplikacje jest jeszcze krótsza od powyższej (nie zawiera Włoch). Twórcy oprogramowania z Polski muszą zatem zdobyć się na dodatkową cierpliwość.

Android Market Developer Distribution Agreement

• Akceptując umowę deweloperską, zgadzamy się na wszystkie zawarte w niej warunki, dlatego konieczne jest zapoznanie się z ca-łą jej zawartością. Warto zwrócić uwagę na to, że umowa zabrania między innymi publikacji programów, które stanowią konku-rencję dla Android Market, jako jedynego oficjalnego źródła dystrybucji aplikacji Android. Obecnie niedozwolone jest również rozpowszechnianie aplikacji umożliwiających tzw. tethering, czyli korzystanie z telefonu jako modemu 3G/GPRS dla komputera osobistego.

• Android Market, tak jak i sam system, nieustannie się rozwija. Niektóre zmiany wymuszają wprowadzanie nowych bądź rozwinięcie istnieją-cych punktów umowy Android Market Developer Distribution Agreement. Bardzo prawdopodobne jest zatem, że po zalogowaniu do kon-ta, z którego regularnie korzystamy, zostaniemy poproszeni o zaakceptowanie nowych warunków umowy. Bez akceptacji aktualnej umowy nie jest możliwe korzystanie z dalszej części serwisu.

• W chwili pisania tego artykułu, możliwy jest wybór jednego z kilku języków umowy, niestety brakuje wciąż wersji polskiej.

Page 96: SDJ Extra 34 Biblia

96

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 97

zarówno zakupu aplikacji, jak i zwrotu po-niesionych kosztów. W rezultacie zakupu odpowiednia kwota pobierana jest z karty płatnika i kierowana na konto, którego da-ne zostały określone w naszym profilu, w przeciągu 2 dni roboczych. Google zastrze-ga sobie przy tym dodatkowe 3 dni, argu-mentując to różnymi trybami przetwarza-nia transakcji przez banki. Przychody wy-generowane przez sprzedawane aplikacje widoczne są w zakładce Payouts konta Go-ogle Checkout. Z pewnością cieszy fakt, że nie istnieje praktycznie żadne minimum bilansu konta, aby możliwe było pobranie zarobionych pieniędzy na konto bankowe – symboliczna kwota minimalna wynosi 1,00 USD.

Spore kontrowersje wśród deweloperów wzbudza polityka zwrotów aplikacji, na którą musimy się zgodzić, publikując swo-je programy na Android Market. Użytkow-nikom przysługuje możliwość anulowania zakupu w czasie jednej doby od momentu pobrania płatnej aplikacji (poprzez jej od-instalowanie). Z tego powodu rzeczywisty okres oczekiwania na gotówkę przez pro-ducenta wydłuża się o 24 godziny. Jeże-li w tym czasie kupujący nie zażąda zwro-tu, dopiero wtedy pobierana jest z jego kar-ty gotówka.

Konto Google Checkout umożliwia rów-nież udzielanie przez dewelopera zwrotów pieniędzy, niezależnie od wyżej wymienio-

nego mechanizmu. Służy do tego przycisk Refund Some Money, po którego wybraniu należy określić numer zamówienia, które-go dotyczy zwrot pieniędzy, powód oraz wysokość zwrotu.

Kupujący zostanie automatycznie po-wiadomiony o zwrocie poprzez e-mail. Po-lecenia zwrotu nie można anulować, jako że jest ono wykonywane natychmiastowo. Jeżeli wysokość zwrotu przekracza bilans konta deweloperskiego, odpowiednia kwo-ta zostanie pobrana wprost z naszego kon-ta bankowego.

Konto Google Checkout oferuje bardzo prosty system przeglądania i raportowania wszystkich transakcji finansowych. W za-kładce Payouts mamy dostęp do następują-cych pozycji:

• Starting Balance – należności będące re-zultatem dotychczasowej sprzedaży;

• Purchases – przychody z bieżącej sprze-daży, które możemy pobrać;

• Other Activity – obsługa nietypowych transakcji, w tym zwrotów;

• Payout – kwota przeznaczona do prze-lewu na konto bankowe (aktualizowana do 24 godzin po wypłacie środków);

• Ending Balance – bilans końcowy po przetworzeniu wypłaty środków.

Wybranie jednej z wymienionych opcji poza bilansem początkowym i końcowym umoż-

liwia wgląd w szczegóły wszystkich transak-cji składających się na daną kwotę, wraz z in-formacją o zamówieniach, z którymi są one związane.

Istnieją dwa rodzaje raportów, jakie mo-żemy wygenerować z konta Google Checko-ut. Są to:

• Payout details – widok wszystkich wy-płat, jakich dokonaliśmy z naszego kon-ta w ciągu danego dnia;

• Transaction details – szczegółowy widok wszystkich płatnych zamówień zrealizo-wanych dla naszego konta w ciągu dane-go dnia.

Aby wygenerować którykolwiek z nich, na-leży wybrać zakładkę Payouts i w sekcji Do-wnload data to spreadsheet (.csv) wybrać inte-resującą nas nazwę raportu. Plik raportu dla określonej przez nas daty zostanie zapisany na dysku. Historia transakcji, dla których możliwe jest stworzenie raportów, obejmu-je okres 2 miesięcy.

Pomocnym narzędziem kontroli dystry-bucji aplikacji jest też główny panel konta dewelopera, który wprawdzie nie daje do-stępu informacji finansowych, ale stano-wi dobre źródło wiedzy o zainteresowaniu aplikacją ze strony użytkowników. W opisa-nej wcześniej sekcji Your Android Market Li-stings widoczne jest porównanie liczby ścią-gnięć i instalacji. Różnica tych dwóch war-

Kategorie aplikacji w Android Market

• Możliwy jest wybór jednego z dwóch typów aplikacji publikowanych za pośrednictwem Android Market: gry lub aplikacje. Każdy z nich dzieli się na szereg kategorii.

• W grupie aplikacji zawartych jest 14 kategorii: Communication, Demo, Entertainment, Finance, Lifestyle, Multimedia, News & Weather, Producti-vity, Reference, Shopping, Social, Software Libraries, Tools, Travel.

• Typ gier podzielony został dotychczas na następujące kategorie: Arcade & Action, Brain & Puzzle, Cards & Casino, Casual.• System nie umożliwia niestety zaznaczenia kilku kategorii dla jednej aplikacji, dlatego wybór musi być dobrze przemyślany i zbieżny z ob-

raną przez nas strategią marketingową. Pewnego rodzaju ucieczką od tego ograniczenia mogłaby się wydawać kilkukrotna publikacja tej samej aplikacji pod różnymi kategoriami, jednak takie rozwiązanie wprowadza więcej zamieszania wśród osób poszukujących aplikacji, niż rzeczywistych korzyści dla dewelopera.

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

• Dyskusji nie podlegają raczej korzyści wynikające z umieszczenia swojej aplikacji w serwisach poświęconych sprzedaży innych niż An-droid Market. Bardzo popularne wśród użytkowników systemu są Handango (http://www.handango.com) oraz Mobihand (http://www.mobihand.com). Zaskakującym może być fakt, że ceny tych samych aplikacji w ramach tych dwóch serwisów bardzo często się różnią. Wynikać to może z nieco innych grup odbiorców, do jakich są one adresowane. Zachęcam również do rozważenia wysłania swojego pro-gramu także do innych serwisów, jak np. specjalizujący się dotychczas w aplikacjach Java GetJar (http://www.getjar.com).

• Warto zaznaczyć swoją obecność i zasygnalizować obecność na rynku nowej aplikacji na forach internetowych poświęconych tematyce An-droid. Do najbardziej znanych należą Talk Android (http://www.talkandroid.com) oraz Android Forums (http://androidforums.com/ ).

• Bardzo popularną i szczególnie godną polecenia stroną oferującą wgląd w aktualną listę dostępnych aplikacji jest Cyrket (http://www.cyrket.com/ ). Strona ta jest klientem Android Market i służy do wygodnego przeglądania, a nie dystrybucji, oprogramowania. Oferuje przy tym wiele możliwości prezentowania wyników, sortowania aplikacji według różnych kryteriów oraz – co najważniejsze i niedostępne na stronie Android Market – pozwala na czytanie wszystkich komentarzy użytkowników.

• Droideo (http://www.droideo.com/ ) – serwis przeznaczony do publikacji wszelkich nagrań wideo dotyczących zarówno urządzeń, jak i apli-kacji adresowanych dla systemu Android. Jest to zarówno ogromne źródło informacji o rynkowych nowościach, jak i świetne medium do publikacji materiałów promocyjnych dla naszych programów.

To jedynie wybrane przykłady spośród ogromnej liczby stron internetowych poświęconych tematyce Android.

Page 97: SDJ Extra 34 Biblia

96

Programowanie Android

SDJ Extra 34 Biblia

Android Market bliżej dewelopera

www.sdjournal.org 97

tości stanowi o liczbie przypadków odinsta-lowania programu. Wskaźnik ten jest dobrą reprezentacją poziomu satysfakcji użytkow-ników z produktu, co ma szczególne znacze-nie w przypadku aplikacji płatnych, dla któ-rych usunięcie programu w przeciągu do-by od instalacji może być związane z żąda-niem zwrotu.

Dość oczywistym parametrem repre-zentującym powodzenie aplikacji na ryn-ku jest liczba gwiazdek przyznawanych przez użytkowników, których średnia war-tość widoczna jest w panelu dewelopera. Z pewnością źródłem bardziej szczegółowych informacji są komentarze pisane przez in-dywidualnych odbiorców naszego oprogra-mowania. Niestety, obecna forma Andro-id Market nie umożliwia przeglądania ko-mentarzy z perspektywy właściciela konta. Możliwość zapoznania się z opiniami zosta-ła dana jedynie samym użytkownikom, któ-rzy mogą je przeczytać po uruchomieniu Android Market zainstalowanego w tele-fonie. Konieczność każdorazowego wyszu-kiwania naszych produktów przez telefon w celu zapoznania się z reakcją użytkowni-ków jest mało wygodnym rozwiązaniem, na szczęście taką funkcjonalność oferują inne strony internetowe.

Żaden system oceniania aplikacji nie za-stąpi bezpośredniej wymiany informacji po-między twórcą a odbiorcą aplikacji. System Android Market na szczęście oferuje możli-wość wysłania wiadomości e-mail przez użyt-kownika. Opcja taka wyświetlana jest pod na-zwą aplikacji widzianej w programie Andro-id Market.

Deweloperzy, których aplikacja uzyskała wysokie notowania w systemie gwiazdko-wym, mogą liczyć na to, że ich program do-stąpi zaszczytu umieszczenia na liście tzw. featured apps. Niestety nie są znane precy-zyjne kryteria zakwalifikowania aplikacji do tego prestiżowego grona, bardzo możli-we, że w pewnym stopniu jest to wynikiem subiektywnej decyzji Google. Aplikacja zaliczona do grupy featured apps bardzo zyskuje na widoczności. Przede wszyst-

kim ikony takich programów wyekspono-wane są ponad standardową listą aplika-cji widzianych przez użytkowników koń-cowych.

Dodatkowo, informacja na temat wy-branych produktów wyświetlona jest na oficjalnej stronie http://www.android.com/market (która nie służy do przeglądania za-wartości platformy i nie zawiera listy pro-gramów), która ponadto umożliwia zapo-znanie się ze zrzutami ekranów działają-cych aplikacji.

Jak być na bieżąco?Nawet jeżeli proces produkcji twojej apli-kacji jest w szczerym polu i nie masz jesz-cze sprecyzowanych planów dotyczących jej sprzedaży, warta rozważenia, pomimo związanego z tym kosztu, jest rejestracja konta dewelopera Android. Zarejestrowa-ni użytkownicy na bieżąco powiadamia-ni są o wszystkich zmianach wprowadza-nych na Android Market. Wszystkie do-tychczasowe informacje związane z po-szerzaniem rynku płatnych czy też dar-mowych aplikacji, zanim trafiły do porta-li informacyjnych, najpierw wysyłane były do zarejestrowanych użytkowników. Posia-danie konta Android gwarantuje zatem in-formacje z pierwszej ręki, co jest niezmier-nie ważne, jeżeli chcemy mieć możliwość dobrego rozpoznania rynku i przemyślanej dystrybucji aplikacji w późniejszym czasie. Posiadanie konta dewelopera daje też moż-liwość zakupu deweloperskiej wersji urzą-dzenia z systemem Android, z którego ce-chami zapoznać się można na stronie An-droid Dev Phone 1: http://android.bright-starcorp.com/ .

Niezależnie od rejestracji konta, jako ofi-cjalne źródło informacji należy traktować stronę http://www.android.com/. To właśnie tutaj ukazały się ogłoszenia dotyczące kon-ferencji Google I/O Developer Conferen-ce czy też przygotowania nowego SDK An-droid 1.5. Również z tej strony z łatwością przeczytamy najświeższe wpisy na blogu, czy to w wątkach typowo technicznych czy

związanych ze sprzedażą oprogramowania na Android Market.

Największym i najbardziej aktywnym źródłem informacji o wszystkich aspek-tach platformy Android jest jednak sekcja Community, z której uzyskamy dostęp do tematycznych grup dyskusyjnych i forów internetowych.

PodsumowanieZbudowanie aplikacji jest uwieńczeniem pra-cy zespołu deweloperskiego, ale to dopiero początek wysiłków innych osób, których za-daniem jest jej skuteczna sprzedaż. Narzę-dziem towarzyszącym platformie Android właściwie od samego jej początku jest Andro-id Market – oficjalna platforma dystrybucji, której głównym założeniem jest powszech-ność dostępu wśród użytkowników urzą-dzeń Android oraz łatwość instalacji. Pomi-mo pewnych braków system wciąż rozwija się i rzeczywiście trafia do coraz większej licz-by odbiorców. Każdy z tych odbiorców może być potencjalnym nabywcą naszego oprogra-mowania.

Artykuł stanowi nie tylko przegląd funkcjonalności Android Market, ale przede wszystkim przybliża go do twórcy oprogramowania jako niezbędne w pracy narzędzie umożliwiające łatwą dystrybu-cję programów na rynku. Informacje tu przedstawione są wystarczające do samo-dzielnej publikacji aplikacji i stanowią so-lidną podstawę do pogłębiania swojej wie-dzy na temat rynku oprogramowania An-droid. Znajomość mechanizmów jego dzia-łania jest konieczna, aby zaistnieć w świa-domości odbiorców.

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.

ADAM SKRZYSZEWSKIProducent w firmie Gamelion Studios. Zadaniem Adama jest prowadzenie projektów związanych z produkcją gier i aplikacji multimedialnych dla platform mobilnych, w tym dla systemu Android.Kontakt z autorem: [email protected]

Page 98: SDJ Extra 34 Biblia

98

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 99

Jak wiadomo, nowych rzeczy najłatwiej i najprzyjemniej uczyć się na przykła-dach. Ta idea przyświeca temu arty-

kułowi, który koncentruje się na pokaza-niu kilku ciekawszych funkcji oferowanych przez Androida oraz sposobie ich wykorzy-stania przy budowaniu aplikacji. Przykła-dowa aplikacja, której stworzenie zaprezen-tuję w tym artykule, nie jest jakoś specjal-nie rozbudowana ani skomplikowana. Jej funkcjonalność można krótko podsumo-wać jednym zdaniem. Umożliwia ona ro-bienie zdjęć i przypisanie im danych geo-graficznych (geotagging) oraz przeglądanie tych zdjęć z poziomu mapy z wykorzysta-niem Google Maps.

Wchodząc w szczegóły, z punktu widze-nia użytkownika można wyróżnić 2 głów-ne moduły aplikacji. Pierwszy odpowia-da za wykonanie zdjęcia, pobranie pozycji geograficznej, oraz zapisanie tego wszyst-kiego w pamięci nieulotnej telefonu. Dru-gi moduł pozwala użytkownikowi na prze-

glądanie zrobionych wcześniej zdjęć. Łącz-nikiem dla tych modułów jest ekran starto-wy pozwalający użytkownikowi wybrać, co chce zrobić.

Pierwszym krokiem będzie stworzenie szkieletu aplikacji oraz jej ekranu starto-wego. Przy okazji zademonstruję, jak ko-rzystać z zasobów dołączonych do aplikacji. Następnie pokażę, jak wykorzystać wbudo-waną w telefon kamerę oraz zapisać na kar-cie pamięci zrobione zdjęcie. Kolejnym kro-kiem będzie dodanie obsługi GPS do pobie-rania danych o lokalizacji oraz skojarzenie tych danych ze zrobionym zdjęciem. Za-demonstruję, jak można w tym celu wy-korzystać bazę danych opartą na SQLite. Na samym końcu pokażę, jak można wy-korzystać Google Maps do prezentacji ko-lekcji zdjęć wykonanych przy pomocy tej aplikacji.

Zachęcam do zapoznania się z kodem źró-dłowym prezentowanej tu aplikacji, który znajduje się na załączonej do pisma płycie. Ze względu na sporą ilość kodu oraz ograniczo-ną ilość miejsca na łamach magazynu nie je-stem w stanie przedstawić listingów dla każ-dej z omawianych funkcji. Uważam tez, że modyfikowanie i eksperymentowanie na go-towym przykładzie może być nawet bardziej

pouczające niż czytanie suchego opisu jego działania i implementacji. Kod jest udostęp-niony jako własność publiczna (public doma-in), więc nie ma żadnych ograniczeń na jego wykorzystanie.

Nowy projektMając gotowe środowisko pracy (patrz ram-ka Szybki start), można przystąpić do utwo-rzenia nowego projektu. Konieczne jest po-danie nazwy projektu, pakietu (org.sdjour-nal.galbum), nazwy aktywności (GAlbu-mActivity) oraz nazwy aplikacji, jaka ma być zaprezentowana użytkownikowi. Eclip-se automatycznie utworzy szkielet aplika-cji (standardowe Hello World!), w którego skład wchodzi:

• deskryptor aplikacji (AndroidMani-fest.xml);

• klasa GAlbumActivity, która jest punk-tem wejścia dla aplikacji;

• katalog z zasobami dołączanymi do apli-kacji, zawierający ikonę oraz pliki XML definiujące zasoby tekstowe oraz układ elementów GUI.

Zawartość wygenerowanej klasy GAlbumActivity ogranicza się do defi-nicji jednej tylko metody i jest przedsta-wiona na Listingu 1. Metoda onCreate() jest wywoływana przez system podczas tworzenia Activity, w przypadku na-szej klasy stanie się tak w odpowiedzi na kliknięcie przez użytkownika ikon-ki aplikacji (takie zachowanie jest zde-finiowane w AndroidManifest.xml, do czego wrócimy w dalszej części artyku-łu). Wywołanie onCreate(Bundle) kla-sy nadrzędnej jest konieczne do po-

Android na przykładzie: geotagging zdjęć

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.

Dowiesz się:• Jak stworzyć aplikację androidową oraz jej

interfejs użytkownika;• Jak skorzystać z kamery oraz GPS;• Jak przechowywać dane, korzystając z syste-

mu plików oraz bazy danych;• Jak skorzystać z Google Maps we własnej

aplikacji.

Powinieneś wiedzieć:• Jak programować w języku Java;• Jak korzystać ze środowiska programistycz-

nego Eclipse.

Poziom trudności

Praktyczne wykorzystanie najciekawszych funkcji mobilnej platformy firmy Google

Page 99: SDJ Extra 34 Biblia

98

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 99

prawnego działania aplikacji. Natomiast setContentView(int) ustawia zawartość ekranu. Osoby, które nie miały styczności z Androidem mogą być nieco zaskoczone parametrem przekazywanym do metody setContentView(int). Szybkie podejrze-nie zawartości klasy R w edytorze ujaw-ni nam, że R.layout.main jest nic nie mó-wiącą stałą numeryczną. Ta magiczna licz-ba to identyfikator zasobu, skompilowa-nego przez narzędzie aapt. W tym wypad-ku odpowiada on plikowi main.xml z kata-logu /res/layout/. W tym katalogu przecho-wywane są tzw. layouty dla UI naszej apli-kacji. Jest to jeden ze sposobów tworzenia interfejsu aplikacji w Androidzie.

Ekran startowyDysponując działającym szkieletem aplika-cji, możemy przystąpić do dzieła. Ekran star-towy ma zawierać dwa przyciski, z których je-den uruchomi moduł aparatu, a drugi galerię zdjęć. Najprostszym sposobem osiągnięcia te-go efektu będzie zmodyfikowanie wygene-rowanego już layoutu. Otworzenie w Eclip-se wspomnianego wcześniej pliku main.xml uruchamia edytor GUI, który pozwala na wy-godne edytowanie hierarchii elementów wi-doku, bez konieczności ręcznych zmian w pliku XML.

Wygenerowany element typu TextView należy usunąć, gdyż nie będzie on po-trzebny. Zostaje nam tylko element typu LinearLayout. Jest to jeden z najprostszych układów, polegający na umieszczaniu kon-trolek liniowo, jedna za drugą. Następnie dodajemy dwa elementy typu Button. W rezultacie otrzymujemy dwa białe, kwadra-

Android: GUIPodstawą 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.

Szybki startZanim 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.

Listing 1. Zawartość wygenerowanej aktywności

package org.sdjournal;

import android.app.Activity;

import android.os.Bundle;

public class GAlbumActivity extends Activity {

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

}

Listing 2. Zawartość pliku main.xml definiującego układ elementów ekranu startowego

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

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_height="wrap_content"

android:layout_width="wrap_content"

android:layout_gravity="center">

<Button

android:layout_height="wrap_content"

android:layout_width="fill_parent"

android:id="@+id/PhotoButton"

android:text="@string/strTakeAPhoto">

</Button>

<Button

android:layout_height="wrap_content"

android:layout_width="fill_parent"

android:id="@+id/GalleryButton"

android:text="@string/strGallery">

</Button>

</LinearLayout>

Page 100: SDJ Extra 34 Biblia

100

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 101

towe przyciski umieszczone jeden pod dru-gim w lewym górnym rogu ekranu. Nie jest to widok specjalnie imponujący, więc spró-bujmy to zmienić. Po pierwsze, przyciski potrzebują etykiet, które rzuciłyby nieco światła na ich przeznaczenie. W tym celu w okienku Properties odnajdujemy właści-wość o nazwie Text i wpisujemy tam ety-kietę dla przycisku (Take a photo oraz Gal-lery). Różna długość tekstu pokazuje nam, że przyciski są dosunięte do lewej stro-ny obszaru, co nie jest specjalnie przyjem-ne dla oka. Aby uzyskać przyciski o tej sa-mej szerokości, wyśrodkowane na ekranie, konieczne jest zmodyfikowanie kilku para-metrów. Po pierwsze, wysokość i szerokość elementu (Layout width i Layout height) mogą być ustalone na stałe lub przyjąć wartość fill_parent lub wrap_content.

fill_parent sprawi, że dany element wy-pełni całkowicie dostępną jemu przestrzeń, wrap_content natomiast użyje jej tylko ty-le, ile jest konieczne, aby wyświetlić da-ny element. Zmiana tych właściwości dla LinearLayout na wrap_content sprawi, że nie będzie wypełniał on całego ekranu. Z kolei za rozmieszczenie elementu od-powiada parametr Layout gravity, usta-wienie go na center umieści nasz layout (i tym samym przyciski) na środku ekra-nu. Aby przyciski miały jednakową sze-rokość, należy ustawić Layout width na fill_parent.

W tym momencie osiągnęliśmy zamie-rzony efekt wizualny, jednak to nie wszyst-ko, co musimy wziąć pod uwagę. Po pierw-sze, do elementów typu przyciski będzie-my musieli odwoływać się z poziomu kodu,

tym samym musimy je jakoś jednoznacz-nie identyfikować. Do tego celu służy pa-rametr Id. Oba przyciski domyślnie posia-dają taki identyfikator, jednak jego wartość jest mało intuicyjna, toteż zmieniamy war-tości domyślne na @+id/PhotoButton oraz @+id/GalleryButton. Po drugie, umiesz-czanie etykiet tekstowych bezpośrednio w layoucie nie jest zbyt rozsądne. Taka prakty-ka sprawia, że lokalizacja takiej aplikacji by-łaby bardzo pracochłonna. Rozwiązaniem jest zdefiniowanie tych etykiet jako zaso-by. W tym celu otwieramy plik strings.xml, i dodajemy dwa elementy typu String (na-zwiemy je strGallery i strTakeAPhoto). Następnie wracamy do edycji layoutu i zmieniamy parametr Text dla obu przyci-sków. Na szczęście Eclipse przychodzi nam 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.setOnClickListener(View.OnClickListener) (pamiętamy, że wszystkie elementy UI dziedziczą po kla-sie View, a więc nasze przyciski też). W tym momencie zapewne zastanawiasz się, drogi Czytelniku, skąd wytrzasnąć obiekt odpo-wiadający przyciskowi zdefiniowanemu w naszym layoucie. Tu do akcji wkraczają zde-finiowane przez nas identyfikatory oraz metoda findViewById(int). Poniżej linij-ka kodu, która robi to, co chcemy:

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

etOnClickListener(m

TakePhotoListener);

Zanim przejdziemy do następnego etapu tworzenia aplikacji, chciałbym przedsta-wić ciekawy mechanizm związany z łado-waniem zasobów przez Androida. Niecier-pliwi mogą przeskoczyć prosto do punk-tu Intencje.

Zasoby a różne konfiguracje Android daje możliwość zdefiniowania al-ternatywnych zasobów w zależności od kon-figuracji środowiska, w którym uruchamia-na jest aplikacja. Aby skorzystać z tej możli-wości, wystarczy alternatywną wersję zasobu Rysunek 1. Diagram cyklu życia aktywności

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

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

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

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

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

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

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

����������

���������

����������

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

�����������

���������

��������

�����������

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

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

Page 101: SDJ Extra 34 Biblia

100

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 101

umieścić w katalogu zawierającym w nazwie jeden lub kilka kwalifikatorów zdefiniowa-nych przez Androida. Takim kwalifikatorem może być np. język, orientacja ekranu czy je-go rozdzielczość. Po szczegółową listę odsy-łam do dokumentacji.

My wykorzystamy ten mechanizm w celu przygotowania ekranu startowe-go przystosowanego specjalnie dla pozio-mej orientacji ekranu. W tym celu otwie-ramy katalog res i robimy kopię podkata-logu layout pod nazwą layout-land. Część nazwy po myślniku jest kwalifikatorem oznaczającym poziomą orientację ekranu. Gdy podajemy ich więcej, wszystkie mu-szą być od siebie oddzielone myślnikiem. Następnie modyfikujemy plik main.xml z nowo utworzonego katalogu. Wybiera-my LinearLayout i zmieniamy parametr Orientation na horizontal, dzięki czemu przyciski zostaną umieszczone poziomo je-den za drugim.

Spróbuj uruchomić tak zmodyfikowaną aplikację na telefonie (albo emulatorze) i zauważ, że gdy zmienisz orientację ekra-nu ([Ctrl+F12] na emulatorze), rozłożenie przycisków zmienia się na to zdefiniowa-ne w alternatywnym layoucie. Nietrudno sobie wyobrazić, jak pomocny może być ten mechanizm, gdy tworzy się aplikację mającą działać na wielu odmiennych pod względem konfiguracji telefonach. Cho-ciaż trzy obecnie dostępne telefony z An-droidem na pokładzie mają prawie iden-tyczną konfigurację, to można się spodzie-wać, że z biegiem czasu pojawią się urzą-dzenia odbiegające parametrami od obec-nej generacji.

No dobrze, wiemy już, jak wykorzystać ten mechanizm i w jakim celu, jednak jak on działa? Odpowiedź na to pytanie jest bardzo prosta. W momencie zmiany kon-figuracji (tutaj: zmiana orientacji ekranu), aktualnie wyświetlana aktywność jest re-startowana, tzn. niszczona, a następnie tworzona ponownie, zgodnie z cyklem ży-cia (patrz ramka Cykl życia aktywności). Aktywności znajdujące się w tle zostaną zrestartowane w momencie ich przywró-cenia na wierzch. Takie zachowanie ozna-cza, że tracimy stan tych aktywności. Wy-obraźmy sobie, że użytkownik jest w trak-cie pisania e-maila, gdy zmiana konfigu-racji wymusza restart aktywności. Utra-ta treści niedokończonej wiadomości jest nieakceptowalna. Na szczęście Andro-

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

Listing 3. Przykład użycia metody onSaveInstanceState(Bundle)

public class EditorActivity extends Activity {

private static final String BUNDLE_KEY = "saved_message";

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

if (savedInstanceState != null) {

String text = savedInstanceState.getString(BUNDLE_KEY);

((EditText) findViewById(R.id.editMsg)).setText(text);

}

}

protected void onSaveInstanceState(Bundle outState) {

EditText editText = (EditText) findViewById(R.id.editMsg);

String message = editText.getText().toString();

outState.putString(BUNDLE_KEY, message);

}

}

Listing 4. Zawartość pliku AndroidManifest.xml dla naszej aplikacji

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="org.sdjournal.galbum"

android:versionCode="1"

android:versionName="1.0.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".GAlbumActivity"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name="PhotoCaptureActivity" android:screenOrientation="land

scape">

</activity>

<activity android:name="PhotoInspectActivity" android:screenOrientation="landscape

"></activity>

<activity android:name="MapGalleryActivity"></activity>

<uses-library android:name="com.google.android.maps"></uses-library>

<activity android:name="PhotoViewActivity" android:configChanges="keyboardHidden|ori

entation" android:screenOrientation="sensor"></activity>

</application>

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-

permission>

<uses-permission android:name="android.permission.CAMERA"></uses-permission>

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

<uses-sdk android:minSdkVersion="3"></uses-sdk>

</manifest>

Page 102: SDJ Extra 34 Biblia

102

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 103

id udostępnia mechanizm, dzięki które-mu możemy zapisać i odtworzyć stan ak-tywności.

Zachowywanie stanuCzas wrócić do metody onCreate(Bundle). Pominięty dotychczas milczeniem para-metr tej metody jest częścią omawianego mechanizmu. Obiekt typu Bundle prze-kazywany przy tworzeniu aktywności mo-że zawierać różnorakie dane, potrzebne do odtworzenia jej stanu. Przy pierwszym uru-chomieniu (lub gdy aktywność nic nie zapi-sała) będzie miał wartość null. Aby skorzy-stać z dobrodziejstw tego mechanizmu, na-leży jakoś te dane zapisać. W tym celu wy-starczy w klasie typu Activity przesłonić metodę onSaveInstanceState(Bundle) i zapisać potrzebne dane, tak jak to pokaza-no na Listingu 3.

Przy restarcie takiej aktywności będzie można w onCreate(Bundle) odczytać zapi-sane dane jedną z metod get.

Skoro wiemy już, jak zareagować na akcję użytkownika, warto, by wreszcie zrobić coś ciekawego. Chcemy, aby kliknięcie w przy-cisk Take a Photo uruchamiało moduł kame-ry, który umożliwi użytkownikowi zrobie-nie zdjęcia.

Zanim jednak przejdziemy do realizacji po-wyższego, proponuję zapoznać się z ramką Anatomia Aplikacji.

IntencjeWiedząc już co nieco o tym, co jest apli-kacją z punktu widzenia Androida, nie-trudno dojść do wniosku, że nasz moduł obsługujący kamerę będzie osobną ak-tywnością. Tworzymy więc nową klasę dziedziczącą po Activity i nazywamy ją

PhotoCaptureActivity. Przesłaniamy me-todę onCreate(Bundle) i mamy już szkie-let aktywności, którą można już urucho-mić. A podstawą mechanizmu urucha-miania aktywności są tzw. intencje, czyli obiekty klasy Intent, które zawierają in-formacje na temat naszych zamiarów. Z ni-mi związane są filtry intencji (IntentFil-ter), definiujące, które komponenty mogą obsłużyć dane intencje. Informacje o tym znajdują się w deskryptorze aplikacji, czyli w pliku AndroidManifest.xml. Otworzenie go z poziomu Eclipse uruchomi wygodny edytor, dzięki któremu nie będziemy mu-sieli ręcznie modyfikować pliku XML. W zakładce Application znajduje się sekcja Application Nodes, zawierająca drzewiasty widok elementów definiujących składniki naszej aplikacji. W naszym przypadku za-wiera definicję głównej aktywności, czy-li GAlbumActivity. Rozwinięcie widoku tego węzła ukaże naszym oczom element Intent Filter oraz dwoje jego dzieci, ak-cję (android.intent.action.MAIN) i kate-gorię (android.intent.category.LAUN-CHER). Więc co to wszystko znaczy?

Aby to łatwiej zrozumieć, przyjrzyjmy się bliżej intencjom. Obiekt klasy Intent może zawierać następujące informacje:

• nazwę komponentu, który ma obsłużyć tę intencję – podajemy tę informację, jeżeli chcemy uruchomić konkretną ak-tywność;

• akcję, którą chcemy wykonać – na pod-stawie samej akcji system może odszu-kać aktywności, które mogą je obsłużyć, często konkretna akcja wymaga dodat-kowych danych;

• adres zasobu w postaci URI – może być wymagany dla konkretnych akcji, przykładowo akcja wyświetlenia obraz-ka może wymagać jego lokalizacji (np. ścieżki do pliku lub adresu interneto-wego);

• kategorię komponentu, który może ob-służyć tę intencję;

• dodatkowe dane w postaci pary klucz-wartość;

• dodatkowe flagi (zdefiniowane w klasie Intent).

Wracając do naszej aplikacji, zdefinio-wany dla niej filtr intencji mówi syste-mowi, że GAlbumActivity jest aktywno-ścią startową naszej aplikacji (akcja MA-IN) oraz że może być wyświetlana na li-ście aplikacji w menu telefonu (kategoria LAUNCHER). Aby aktywność mogła zo-stać uruchomiona, musi zostać zdefinio-wana w AndroidManifest.xml. W tym ce-lu dodajemy naszą nowo utworzoną ak-tywność do listy, używając przycisku Add w sekcji Application Nodes i ustawia-

Listing 5. Kod aktywności startowej naszej aplikacji

public class GAlbumActivity extends Activity {

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

((Button) findViewById(R.id.GalleryButton))

.setOnClickListener(mGalleryListener);

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

.setOnClickListener(mTakePhotoListener);

PhotoManager.init(this);

}

private View.OnClickListener mGalleryListener = new View.OnClickListener() {

public void onClick(View view) {

launchActivity("MapGalleryActivity");

}

};

private View.OnClickListener mTakePhotoListener = new View.OnClickListener() {

public void onClick(View view) {

launchActivity("PhotoCaptureActivity");

}

};

private void launchActivity(String activityName) {

ComponentName componentName = new ComponentName("org.sdjournal.galbum",

"org.sdjournal.galbum." + activityName);

Intent runActivity = new Intent();

runActivity.setComponent(componentName);

startActivity(runActivity);

}

protected void onDestroy() {

PhotoManager.release();

}

}

Page 103: SDJ Extra 34 Biblia

102

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 103

jąc dla niej atrybut Name. Kliknięcie przy-cisku Browse wyświetli listę znalezionych aktywności w projekcie, więc nie ma po-trzeby ręcznego wpisywania nazwy ak-tywności.

Dla ciekawskich finalna zawartość mani-festu przedstawiona jest na Listingu 4. Znak @ na początku niektórych parametrów ozna-cza, że jest on referencją do zasobu.

Uruchamianie aktywnościMamy już naszą nową aktywność zdefinio-waną w manifeście, więc czas najwyższy pokazać, jak ją uruchomić. Jako że wiemy dokładnie, którą aktywność chcemy uru-chomić, to musimy utworzyć intencję za-wierającą nazwę komponentu. Proces ten jest bardzo prosty. Tworzymy obiekt klasy ComponentName zawierający nazwę pakie-tu, w którym znajduje się aktywność, któ-rą chcemy uruchomić, oraz pełną nazwę klasy (wraz z pakietem). I tu mała, aczkol-wiek istotna, uwaga. Często w dokumen-tacji, jak i w samym manifeście pojawia się termin package.

Nie zawsze jednak oznacza on pakiet w sensie używanym przez Javę. Tym termi-nem określa się nazwę paczki aplikacji. Jest to unikalny identyfikator zdefiniowany w manifeście. W naszym przypadku jest to org.sdjournal.galbum, co odpowiada również pakietowi w rozumieniu Javy, jednak wcale tak nie musi być.

Mając tę drobnostkę wyjaśnioną, kon-tynuujemy pracę. Tworzymy obiekt kla-sy Intent i wywołujemy jego metodę setComponent(ComponentName) z wcześniej stworzoną nazwą komponentu. W tym momencie mamy gotową intencję, którą przekazujemy jako parametr do metody startActivity(Intent) z klasy Activity. Efektem tych działań będzie czarny ekran, gdyż nie ustawiliśmy żadnego widoku dla nowej aktywności. Aby wrócić do ekranu 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 efekt. Kod źródłowy naszej aktywności startowej można znaleźć na Listingu 5.

Obsługa kameryNa pewno masz już, drogi Czytelniku, dość opisu podstaw programowania aplikacji an-droidowej. Obiecuję, że od tego momen-tu zajmiemy się zdecydowanie ciekawszy-mi rzeczami.

Zacznijmy od zdefiniowania, co chcemy osiągnąć. Po uruchomieniu modułu kame-ry oczom użytkownika powinien ukazać 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-

cji auto focus, status usługi pozycjonowania (np. GPS) czy informacje o parametrach zdjęcia lub dostępnej pojemności na karcie pamięci. Po zrobieniu zdjęcia użytkownik powinien mieć możliwość przyjrzenia mu

się z bliska oraz podjęcia decyzji, czy je za-pisać, czy nie.

Zanim przystąpimy do pisania kodu ob-sługującego kamerę, musimy ustawić od-powiednie uprawnienia dla naszej aplika-

Listing 6. Kod odpowiedzialny za tworzenie i inicjalizację kontrolera kamery

public class CameraController {

private static CameraController mInstance;

private Camera mCamera;

private CameraController() {

mCamera = Camera.open();

}

public static CameraController getCameraController() {

if (mInstance == null) {

mInstance = new CameraController();

}

return mInstance;

}

public void release() {

if (mCamera != null) {

mCamera.stopPreview();

mCamera.release();

mCamera = null;

}

mInstance = null;

}

Listing 7. Kod callbacka zarządzającego powierzchnią podglądu

private SurfaceHolder.Callback mSurfaceHolderCallback = new

SurfaceHolder.Callback() {

public void surfaceCreated(SurfaceHolder holder) {

try {

mCamera.setPreviewDisplay(holder);

} catch (IOException e) {

e.printStackTrace();

}

}

public void surfaceChanged(SurfaceHolder holder, int format, int width,

int height) {

if (mCamera != null) {

Camera.Parameters parameters = mCamera.getParameters();

parameters.setPreviewSize(width, height);

mCamera.setParameters(parameters);

mCamera.startPreview();

}

}

public void surfaceDestroyed(SurfaceHolder holder) {

if (mCamera != null) {

mCamera.stopPreview();

}

}

};

Page 104: SDJ Extra 34 Biblia

104

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 105

cji. W tym celu otwieramy AndroidMani-fest.xml na zakładce Permissions i dodaje-

my wpis Uses Permission z parametrem android.permission.CAMERA. Taki wpis

w manifeście informuje system, że nasza aplikacja korzysta z danej funkcjonalno-ści i potrzebuje w tym celu pozwolenia. Użytkownik zostanie o tym poinformo-wany podczas instalacji. Skoro już jeste-śmy przy edytowaniu manifestu, ustawia-my poziomą (landscape) orientację ekra-nu dla PhotoCaptureActivity (atrybut Screen orientation). Teraz możemy za-cząć zabawę.

Pierwszym krokiem do zaimplemento-wania opisanej wcześniej funkcjonalności 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 klasami i interfejsami.

Zaczniemy od napisania klasy pomocni-czej, która nieco uprości korzystanie z ka-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-ro pracy przy przenoszeniu kodu ze sta-rego na nowe API. To oczywiście ma ma-łe znaczenie dla kodu, który stanowi tyl-ko przykład na potrzeby tego artykułu, ale dobrze o takich rzeczach pamiętać w przy-padku pisania aplikacji produkcyjnych. Po drugie, łatwiej jest w ten sposób pokazać, jak korzystać z nieznanej funkcjonalności. Po trzecie, nieumiejętnie obchodząc się z zasobem kamery, możemy Androidowi zrobić kuku. A to dlatego, że kamera nie jest zasobem współdzielonym. Zaczynając pracę z kamerą, musimy o tym pamiętać i sprzątać po sobie, w przeciwnym wypad-ku możemy ten zasób permanentnie za-blokować. I tu drobna rada. Jeżeli ekspery-mentujesz z kodem, który korzysta z API dla kamery i coś pójdzie nie tak, powodu-jąc nieoczekiwane zamknięcie aplikacji, to 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ę-cie, to znak, że możesz kontynuować swoje eksperymenty. Jeżeli jednak aplikacja nie działa, jak należy, to znaczy, że twój kod robi z kamerą straszne rzeczy. W takiej sy-tuacji po prostu zrestartuj telefon. Dodam tylko, że osobiście udało mi się kilkakrot-nie doprowadzić do drugiej z powyższych sytuacji.

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-go kontrolera. Zastosujemy podejście opar-te na zdarzeniach, tzn. użytkownik kontro-lera będzie informowany o interesujących go wydarzeniach poprzez zarejestrowane obiekty nasłuchujące. Oznacza to, że sam

Listing 8. Kod odpowiedzialny za ustawianie podglądu i parametrów zdjęcia

public void setPhotoQuality(int quality) {

if (mCamera != null) {

if (quality < 0 || quality > 100) {

quality = 80;

}

Parameters params = mCamera.getParameters();

params.set("jpeg-quality", quality);

mCamera.setParameters(params);

}

}

public void setPreview(SurfaceHolder holder) {

if (mCamera != null) {

holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

holder.addCallback(mSurfaceHolderCallback);

}

}

Listing 9. Kod odpowiedzialny za obsługę auto focusa

public interface FocusChangeListener {

int AUTO_FOCUS_READY = 0;

int AUTO_FOCUS_IN_PROGRESS = 1;

int AUTO_FOCUS_FAILED = 2;

int AUTO_FOCUS_SUCCEDED = 3;

public void onCameraFocusChange(int focusState);

}

private FocusChangeListener mFocusChangeListener;

public void setFocusChangeListener(FocusChangeListener listener) {

mFocusChangeListener = listener;

}

private void focusChangeNotify(int newState) {

if (mFocusChangeListener != null) {

mFocusChangeListener.onCameraFocusChange(newState);

}

}

private Camera.AutoFocusCallback mAutoFocusCallback = new Camera.AutoFocusCal

lback() {

public void onAutoFocus(boolean success, Camera camera) {

if (success) {

focusChangeNotify(FocusChangeListener.AUTO_FOCUS_SUCCEDED);

} else {

focusChangeNotify(FocusChangeListener.AUTO_FOCUS_FAILED);

}

}

};

public void autoFocus() {

focusChangeNotify(FocusChangeListener.AUTO_FOCUS_IN_PROGRESS);

mCamera.autoFocus(mAutoFocusCallback);

}

Page 105: SDJ Extra 34 Biblia

104

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 105

kontroler nie przechowuje np. stanu funk-cji auto focus ani danych właśnie zrobione-go zdjęcia. Jest jedynie biernym przekaźni-kiem informacji.

Zacznijmy od inicjalizacji. Statyczna metoda Camera.open() zwróci nam in-stancję klasy Camera, na której będziemy dalej operować. W momencie zakończe-nia pracy z kamerą należy wywołać jej me-todę release(), która zwolni zasób kame-ry, umożliwiając korzystanie z niego in-nym aplikacjom. Prywatny konstruktor i statyczna metoda CameraController.getCameraController() uniemożliwiają próbę stworzenia więcej niż jednej instancji ka-mery. Czujni czytelnicy zapewne zauważą szachrajstwo w metodzie release(), które wraz ze zwolnieniem zasobów nulluje in-stancję kontrolera. Oznacza to, że de facto nie jest on singletonem, gdyż po jego zwol-nieniu metoda getCameraController() zwróci nową jego instancję. Możliwe jest więc istnienie kilku kontrolerów, jednak wciąż mamy gwarancję, że tylko jeden z nich jest powiązany z kamerą. Jest to za-bieg celowy. W dalszej części artykułu po-każę również, dlaczego jest to złe rozwią-zanie.

Inicjalizację (której kod przedstawio-ny jest na Listingu 6) mamy z głowy, czas ustawić podgląd kamery. Do tego celu po-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 sobie posiadacz). Najczęściej obiekt ten jest pozyskiwany z obiektu klasy SurfaceView, który z kolei zapewnia bezpośredni dostęp do powierzchni ekranu (będziemy jeszcze 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 na rzecz posiadacza. SURFACE_TYPE_PUSH_BUFFERS oznacza, że dana powierzchnia nie posiada własnych buforów. Ustawie-nie takiego typu jest konieczne, gdyż bu-forami podglądu zarządza usługa kamery.

Po co nam natomiast callback? Otóż dzięki niemu zostaniemy poinformowani o fak-cie stworzenia, zmiany i zniszczenia ob-szaru zdatnego do wyświetlania. Próba na-rysowania podglądu na powierzchni nieza-inicjalizowanej lub zniszczonej skończy-łaby się niepowodzeniem. Dlatego zanim ustawimy podgląd, musimy upewnić się, że powierzchnia, na której będzie on ry-sowany, istnieje. Zanim podgląd włączy-my, musimy ustawić poprawne parame-try, a w momencie zniszczenia powierzch-ni musimy zadbać o zatrzymanie podglą-du. Do tego właśnie wykorzystamy ów cal-lback, którego kod możemy podziwiać na

Listingu 7. I tak w surfaceCreated() usta-wiamy powierzchnię dla podglądu, korzy-stając z metody setPreviewDisplay(SurfaceHolder). W surfaceChanged() mamy już gwarancję, że inicjalizacja powierzch-ni już się zakończyła, ustawiamy więc roz-miar podglądu pasujący do rozmiaru po-wierzchni i rozpoczynamy podgląd. W surfaceDestroyed() zatrzymujemy pod-gląd, gdyż dalsza próba jego wyświetlania na zniszczonej powierzchni zakończy się niepowodzeniem.

Metoda setPhotoQuality(int) usta-wia jakość zdjęcia, czyli stopień kompre-sji obrazu JPEG, który zostanie stwo-

Listing 10. Kod odpowiedzialny za wykonanie zdjęcia

public interface PhotoCaptureListener {

public void onJpgPhotoCaptured(byte[] jpgData);

}

private PhotoCaptureListener mCaptureChangeListener;

public void setCaptureStateListener(PhotoCaptureListener l) {

mCaptureChangeListener = l;

}

private void photoCapturedNotify(byte[] data) {

if (mCaptureChangeListener != null) {

mCaptureChangeListener.onJpgPhotoCaptured(data);

}

}

Camera.PictureCallback jpgPictureCallback = new Camera.PictureCallback() {

public void onPictureTaken(byte[] data, Camera camera) {

photoCapturedNotify(data);

focusChangeNotify(FocusChangeListener.AUTO_FOCUS_READY);

mCamera.startPreview();

}

};

public void capture() {

if (mCamera != null) {

mCamera.takePicture(null, null, jpgPictureCallback);

}

}

Anatomia aplikacjiGłó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.

Page 106: SDJ Extra 34 Biblia

106

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 107

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

public class PhotoCaptureActivity extends Activity {

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.camera_preview);

initCamera();

initGps();

}

CameraController mCameraController;

private void initCamera() {

SurfaceView previewView = (SurfaceView) findViewById(R.id.previewSurfaceView);

mCameraController = CameraController.getCameraController();

mCameraController.setPreview(previewView.getHolder());

mCameraController.setFocusChangeListener(mFocusListener);

mCameraController.setCaptureStateListener(mPhotoListener);

findViewById(R.id.cameraLayout).setOnKeyListener(mKeyListener);

}

protected void onDestroy() {

super.onDestroy();

mCameraController.release();

}

private CameraController.FocusChangeListener mFocusListener = new CameraController.FocusChangeListener() {

public void onCameraFocusChange(int state) {

ImageView afIcon = (ImageView) findViewById(R.id.focusIndicator);

switch (state) {

case AUTO_FOCUS_READY:

afIcon.setImageResource(R.drawable.af_none); break;

case AUTO_FOCUS_IN_PROGRESS:

afIcon.setImageResource(R.drawable.af_yellow); break;

case AUTO_FOCUS_SUCCEDED:

afIcon.setImageResource(R.drawable.af_green); break;

case AUTO_FOCUS_FAILED:

afIcon.setImageResource(R.drawable.af_red); break;

}

}

};

private CameraController.PhotoCaptureListener mPhotoListener = new CameraController.PhotoCaptureListener() {

public void onJpgPhotoCaptured(byte[] jpgData) {

PhotoManager.addPendingPhoto(jpgData, mLastLocation);

launchPhotoInspectActivity();

};

};

private View.OnKeyListener mKeyListener = new View.OnKeyListener() {

public boolean onKey(View v, int keyCode, KeyEvent event) {

if (keyCode == KeyEvent.KEYCODE_FOCUS) {

if (event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN) {

mCameraController.autoFocus();

}

return true;

} else if (keyCode == KeyEvent.KEYCODE_CAMERA) {

if (event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN) {

mCameraController.capture();

}

return true;

}

return false;

}

};

}

Page 107: SDJ Extra 34 Biblia

106

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 107

rzony przez moduł kamery. Korzysta-my z metody getParameters(), aby po-brać aktualne parametry kamery. Parame-try te przechowywane są w obiekcie kla-sy Camera.Parameters. Po ustawieniu żą-danego parametru, za pomocą metody set(String, int) przekazujemy je do ka-mery używając metody setParameters(). Kod odpowiedzialny za ustawianie pod-glądu i parametrów zdjęcia przedstawia Li-sting 8.

Kolejnym krokiem będzie obsługa funk-cji auto focus. Robienie zdjęcia prawdzi-wym aparatem cyfrowym z reguły odby-wa się dwustopniowo. Użytkownik wciska nieznacznie przycisk migawki i czeka aż funkcja auto focus ustawi ostrość i da mu znać, że skończyła. Wtedy wciśnięcie przy-cisku do końca powoduje zrobienie wspa-niałego, ostrego jak brzytwa zdjęcia. Do-brze by było zapewnić użytkownikowi na-szej aplikacji podobne emocje, dzięki cze-mu może na chwilę zapomni, że jego zdję-cie nie będzie tak wspaniałe i ostre jak to wykonane aparatem cyfrowym kosztują-cym połowę tego co jego telefon. Zacznij-my od tego, że funkcję auto focus kamery uruchamiamy, wywołując adekwatnie na-zwaną metodę autoFocus() na obiekcie kamery. Metoda ta jako parametr przyj-muje obiekt implementujący interfejs Camera.AutoFocusCallback , dzięki któ-remu możemy się dowiedzieć, jaki był re-zultat działania auto focusa. To wystarczy, aby nasz kontroler wiedział, co się dzieje, teraz wystarczy to przekazać użytkowniko-wi kontrolera.

My zamiast stosować konwencję udało się lub nie, wprowadzimy cztery stany dla auto focusa. Będą to:

• gotowy do użycia (np. bezpośrednio po uruchomieniu kamery lub po zrobieniu zdjęcia);

• w trakcie działania;• zakończony niepowodzeniem;• zakończony powodzeniem.

Nie będziemy specjalnie oryginalni w kwestii mechanizmu informowania użyt-kownika o zmianie stanu i wykorzysta-my powszechnie używany mechanizm callbacków (zwanych również z angielska listenerami lub obiektami nasłuchujący-mi). Do tego celu posłuży interfejs CameraController.FocusChangeListener, me-toda setFocusChangeListener() do re-jestrowania callbacka oraz prywatna me-toda pomocnicza focusChangeNotify(). Do uruchomienia całej tej machinerii służy jakże oryginalnie nazwana metoda autoFocus() naszego kontrolera. Kod ob-sługujący funkcję auto focus można zna-leźć na Listingu 9.

Listing 12. Kod inicjalizujący GPS

private void initGps() {

mGpsStatusLocationIndicator = (TextView) findViewById(R.id.gpsLocation);

mGpsStatusIndicator = (TextView) findViewById(R.id.gpsStatus);

mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

boolean isGpsEnabled = mLocationManager.isProviderEnabled(LocationM

anager.GPS_PROVIDER);

if (isGpsEnabled) {

mGpsStatusIndicator.setText(R.string.gpsEnabled);

}

}

Listing 13. Kod odpowiadający za pobieranie pozycji geograficznej

private static final int GPS_UPDATE_TIME = 5000;

protected void onResume() {

super.onResume();

mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,

GPS_UPDATE_TIME, 0, mLocationListener);

}

protected void onPause() {

super.onPause();

mLocationManager.removeUpdates(mLocationListener);

}

protected void onDestroy() {

super.onDestroy();

mCameraController.release();

mGpsStatusIndicator = null;

mGpsStatusLocationIndicator = null;

mCameraController = null;

mLocationManager = null;

}

private LocationListener mLocationListener = new LocationListener() {

public void onLocationChanged(android.location.Location location) {

synchronized (this) {

mLastLocation = location;

}

updateLocationIndicator(location);

};

public void onProviderDisabled(String provider) {

mGpsStatusIndicator.setText(R.string.gpsDisabled);

updateLocationIndicator(null);

};

public void onProviderEnabled(String provider) {

mGpsStatusIndicator.setText(R.string.gpsEnabled);

};

public void onStatusChanged(String provider, int status, Bundle extras) {

switch (status) {

case LocationProvider.OUT_OF_SERVICE:

mGpsStatusIndicator.setText(R.string.gpsOutOfService);

updateLocationIndicator(null);

break;

case LocationProvider.TEMPORARILY_UNAVAILABLE:

mGpsStatusIndicator.setText(R.string.gpsUnavailable);

updateLocationIndicator(null);

break;

case LocationProvider.AVAILABLE:

mGpsStatusIndicator.setText(R.string.gpsAvailable);

break;

}

};

};

Page 108: SDJ Extra 34 Biblia

108

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 109

Pozostało nam już tylko zaimplemen-towanie najważniejszej funkcji, czyli zro-bienie zdjęcia. Do tego celu nasz kontro-ler udostępnia metodę capture(), która pod maską wywołuje na kamerze metodę takePicture(). Tak jak w przypadku meto-dy autoFocus(), korzysta ona z callbacków, aby poinformować użytkownika o rezulta-tach jej działania. I tak ShutterCallback przekazuje informację o fakcie zamknię-cia migawki, co oznacza moment wykona-nia zdjęcia, kiedy dane nie są jeszcze do-stępne. Jednak dużo istotniejszą rolę peł-ni PictureCallback, gdyż to za jego pomo-cą możemy pozyskać wynik działania me-tody takePicture(), czyli dane samego zdjęcia. Mamy możliwość zarejestrowania dwóch takich callbacków, jednego dla suro-wych danych, drugiego dla formatu JPEG. Nasz kontroler jest zainteresowany wyłącz-nie obrazem w formacie JPEG, które to da-ne przekazuje na zewnątrz mechanizmem analogicznym do przekazywania stanu au-to focusa. Gotowy kod można znaleźć na Li-stingu 10.

Mając gotowy kontroler, wystarczy wy-korzystać go w naszej aplikacji. Zaczynamy od utworzenia layoutu dla widoku kamery. Nie będzie on zbyt skomplikowany. Użyje-my RelativeLayout do rozłożenia naszych kontrolek, których zresztą nie będzie wie-

le. SurfaceView, który będzie wypełniał ca-łą dostępną powierzchnię ekranu, posłuży do wyświetlania podglądu. Natomiast do pokazania ikonki statusu auto focusa użyje-my ImageView. Będzie on wyświetlał jeden z czterech obrazków w zależności od stanu. Obrazki znajdują się w podkatalogu drawa-ble, dzięki czemu są dostępne przy użyciu mechanizmu zarządzania zasobami Andro-ida. Dodatkowo wstawimy dwie kontrolki TextView, które posłużą nam do wyświetla-nia informacji o statusie GPS.

Kod gotowej aktywności przedstawio-ny jest na Listingu 11. Dzięki wykorzysta-niu naszego kontrolera powinien on być stosunkowo czytelny. Podsumowując, oto, co się tam dzieje. Na początku, za pomo-cą metody requestWindowFeature() po-zbywamy się belki tytułowej, aby uzyskać większą powierzchnię dla podglądu. Na-stępnie metoda initCamera() ustawia layout oraz pobiera obiekt SurfaceView, który posłuży nam do ustawienia pod-glądu. Tworzymy kontroler kamery, ini-cjalizujemy podgląd oraz rejestrujemy obiekty klas FocusChangeListener oraz PhotoCaptureListener. Na końcu rejestru-jemy w głównym widoku OnKeyListener, dzięki któremu będziemy informowani o wciśnięciach klawiszy. I tutaj mała, aczkol-wiek istotna, uwaga. Abyśmy mogli być in-

formowani o zdarzeniach związanych z in-terakcją użytkownika (np. wciśnięcia kla-wiszy), widok, który podsłuchujemy, mu-si mieć możliwość zyskania focusa. Moż-na to uzyskać, wywołując na nim metodę setFocusable(true) lub setFocusableInTouchMode(true) , lub ustawiając analogicz-ne atrybuty w definicji layoutu.

Obsługa przycisków jest trywialna. W metodzie onKey() sprawdzamy, czy kod klawisza odpowiada przyciskowi auto focu-sa lub kamery. Jeżeli tak, to bierzemy pod uwagę tylko pierwsze z serii zdarzeń gene-rowanych przez wciśnięcie klawisza i wy-wołujemy pożądaną metodę naszego kon-trolera kamery. Zwrócenie true oznacza, że obsłużyliśmy zdarzenie i tym samym nie jest ono propagowane dalej. Gdybyśmy po-minęli ten szczegół, zdarzenie o wciśnię-ciu klawisza kamery trafiłoby do systemu, gdzie spowodowałoby wygenerowanie in-tencji CAMERA_BUTTON, co doprowadziło-by do uruchomienia systemowej aplikacji Camera.

Ikonkę stanu auto focusa podmieniamy w metodzie onCameraFocusChange(). Na-tomiast w onJpgPhotoCaptured() otrzy-mujemy dane zdjęcia. Przeciążoną meto-dę onDestroy() używamy do zwolnienia zasobów używanych przez kontroler ka-mery.

W tym momencie mamy gotową ak-tywność do obsługi aparatu i możemy jej użyć do zrobienia zdjęcia. Jest to też do-bry moment, żeby pokazać konsekwen-cje złego zaprojektowania naszego kontro-lera kamery. Podczas działania naszej apli-kacji przejdź do ekranu startowego telefo-nu, wciskając przycisk home, i uruchom aplikację systemową Camera. Efektem bę-dzie okienko z wiadomością o wystąpie-niu błędu. Dzieje się tak dlatego, że nasza aktywność wciąż używa zasobu kamery. Owszem, zatrzymuje podgląd w momen-cie, gdy aplikacja nie jest na widoku, jed-nak zwolnienie kamery następuje dopiero w momencie zamknięcia aktywności. Ma-ło tego, system nie gwarantuje, że metoda onDestroy() zostanie wywołana. W szcze-gólnych przypadkach system może zabić aktywność, kończąc jej cykl życia na meto-dzie onPause(). W takim przypadku użyt-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 aplikacje na Androida, musisz pamiętać, że jest to system wielozadaniowy, a Two-ja aplikacja może być jedynie jedną z wie-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ąć

Listing 14. Klasa widoku zdjęcia

public class FullscreenImageView extends View {

Bitmap image;

private int width;

private int height;

private float xPos;

private float yPos;

public FullscreenImageView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public FullscreenImageView(Context context) {

super(context);

}

public void setImage(Bitmap bitmap) {

image = bitmap;

width = bitmap.getWidth();

height = bitmap.getHeight();

}

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (image != null) {

canvas.drawBitmap(image, xPos, yPos, null);

}

}

}

Page 109: SDJ Extra 34 Biblia

108

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 109

tego typu problemów, pozostawiam Czy-telnikowi.

Pozyskiwanie pozycji geograficznejSkoro możemy już zrobić zdjęcie, zajmijmy się drugą funkcją naszej aplikacji, czyli geotag-gingiem. Chcemy do zdjęcia przypisać dane o pozycji geograficznej. Oczywiście pierwszym krokiem jest pozyskanie tych informacji. Do tego celu wykorzystamy wbudowany w tele-fon odbiornik GPS.

Udostępniane przez Androida Location API jest wyjątkowo proste w użyciu i wystar-czy kilka linijek kodu, aby zyskać dostęp do informacji o położeniu geograficznym tele-fonu. Klasy związane z usługą lokalizacyjną znajdują się w pakiecie android.location. Nam potrzebne będą LocationManager oraz LocationListener. Pierwsza umożli-wia dostęp do systemowej usługi lokaliza-cyjnej, natomiast LocationListener jest interfejsem, za pośrednictwem którego je-steśmy informowani o zmianie położenia. Ponadto aplikacja korzystająca z GPSa mu-si ustawić w swoim manifeście odpowied-nie uprawnienia, analogicznie jak dla kame-ry. Czyli dodajemy wpis Uses Permission z parametrem android.permission.ACCESS_FINE_LOCATION.

Tym razem podarujemy sobie opa-kowywanie androidowego API i użyje-my wyżej wspomnianych klas bezpo-średnio z poziomu naszej aktywności. Po pierwsze, musimy stworzyć instan-cję klasy LocationManager , za pośred-nictwem której będziemy mogli korzy-stać z usługi lokalizacyjnej. Do tego słu-ży metoda getSystemService(String) , której jako parametr podajemy nazwę żą-danej usługi, czyli w naszym przypadku Context.LOCATION_SERVICE. Robimy to podczas tworzenia aktywności, czyli w me-todzie onCreate(). Zajmuje się tym meto-da initGps(), której kod źródłowy przed-stawiony jest na Listingu 12. Tym razem będziemy chcieli obejść się łaskawie z za-sobami, co oznacza, że ograniczymy się do korzystania z GPSu tylko gdy aktywność jest na pierwszym planie. Stąd obecność przesłoniętych metod onResume() oraz onPause(). Po opis cyklu życia aktywności odsyłam do ramki.

Jak wspomniałem wcześniej, dane o zmianie pozycji geograficznej przekazywa-ne są poprzez interfejs LocationListener, a konkretnie jego metodę onLocationChanged(Location). Pozostałe trzy metody służą do informowania o zmianie statusu usługi. Tworzymy obiekt implementujący ten interfejs i przekazujemy go jako para-metr do metody LocationManager.requestLocationUpdates(), wraz z typem usłu-gi lokalizacyjnej (LocationManager.GPS_

PROVIDER), minimalnym odstępem cza-sowym pomiędzy aktualizacjami pozycji oraz minimalną odległością, po której no-wa pozycja zostanie przekazana. Wywoła-nie tej metody następuje w onResume(), natomiast w onPause() z pomocą metody LocationManager .removeUpdates() in-formujemy usługę lokalizacyjną, że nie je-steśmy już zainteresowani aktualizacjami pozycji. Dzięki temu system może zmniej-

szyć zużycie baterii, np. wyłączając odbior-nik GPS w chwili, kiedy użytkownik nie korzysta z naszej aplikacji. Gdybyśmy za-pomnieli o wywołaniu removeUpdates(), to nasz LocationListener wciąż otrzymy-wałby aktualizacje pozycji, wymuszając ak-tywność GPSa nawet po wyjściu z aplika-cji. Jeżeli natomiast w onDestroy() wynul-lujemy obiekty, do których nasz listener się odwołuje, to na wyjściu z aplikacji użyt-

Listing 15. Obsługa przewijania zdjęcia za pomocą ekranu dotykowego

private float lastX;

private float lastY;

private void touchStart(float x, float y) {

lastX = x;

lastY = y;

}

private void touchUpdate(float x, float y) {

lastX = x;

lastY = y;

}

private void updatePosition(final float x, final float y) {

float xOff = x - lastX;

float yOff = y - lastY;

if (width > getWidth()) {

xPos += xOff;

xPos = Math.min(xPos, 0);

xPos = Math.max(xPos, -(width - getWidth()));

}

if (height > getHeight()) {

yPos += yOff;

yPos = Math.min(yPos, 0);

yPos = Math.max(yPos, -(height - getHeight()));

}

}

public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();

float y = event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

touchStart(x, y);

invalidate();

break;

case MotionEvent.ACTION_MOVE:

updatePosition(x, y);

invalidate();

break;

default:

break;

}

touchUpdate(x, y);

return true;

}

Page 110: SDJ Extra 34 Biblia

110

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 111

kownik zobaczy komunikat o błędzie po-chodzącym z aplikacji, z której właśnie wy-szedł. Dlatego podkreślam konieczność do-kładnego sprzątania po sobie. Opisany po-wyżej kod znajduje się na Listingu 13. Me-toda updateLocationIndicator() aktuali-zuje kontrolkę tekstową wyświetlaną na podglądzie zdjęcia i zawierającą dane o ak-tualnej pozycji.

Wyświetlanie zdjęciaZgodnie z początkowym opisem funkcjo-nalności, użytkownik powinien mieć moż-liwość obejrzenia zdjęcia w pełnej krasie, zanim zdecyduje się je zapisać. W tym ce-lu skorzystamy z niskopoziomowych opera-cji rysowania i stworzymy własną klasę wi-doku.

Chcemy, aby nasz widok wyświetlał zdjęcie w jego rzeczywistej rozdzielczości oraz umożliwiał użytkownikowi przewi-janie w wypadku, gdy zdjęcie jest większe niż powierzchnia ekranu. Oprócz tego, na ekranie znajdą się dwa przyciski, z których jeden powoduje zapisanie zdjęcia, a drugi jego skasowanie.

Po wciśnięciu któregokolwiek z nich ak-tywność jest zamykana i użytkownik powra-ca do podglądu kamery.

Na szczęście zadanie jest bardzo pro-ste. Tworzymy klasę FullscreenImageView dziedziczącą po View. Dodajemy pole ty-pu Bitmap, które będzie przechowywa-ło zdjęcie w postaci nadającej się do ryso-wania. Następnie przesłaniamy metodę onDraw(Canvas), w której odbywać się bę-dzie rysowanie. Metody służące do ryso-wania są udostępniane przez obiekt, któ-ry dostajemy jako argument onDraw(). Sam kod rysujący zdjęcie to tylko sprawdzenie, czy bitmapa istnieje, i wywołanie metody drawBitmap(), która ją rysuje w zadanej po-zycji. Ostatni parametr służy do przekaza-nia dodatkowych informacji o sposobie ry-sowania, za pomocą którego możemy np. używać filtrów. My go ignorujemy, przeka-zując null. Kod naszej klasy widoku przed-stawiony jest na Listingu 14.

Większa część kodu naszej klasy to obsłu-ga zdarzeń pochodzących z ekranu dotyko-wego. można go znaleźć na Listingu 15. Wcześniej używaliśmy w podobnym celu listenerów, jednak w tym przypadku mo-żemy przesłonić metodę onTouchEvent() (analogicznie możemy postąpić dla klawi-szy i trackballa). Pamiętajmy, że aby nasz widok otrzymywał zdarzenia, musi mieć ustawiony atrybut Focusable. W tej meto-

dzie aktualizujemy pozycję wyświetlanej bitmapy zgodnie ze zmianami położenia palca na ekranie dotykowym. Po aktuali-zacji wywołujemy metodę invalidate(), która informuje system, że życzymy sobie, aby widok został odrysowany. Nie powo-duje to jednak natychmiastowego odświe-żenia ekranu w momencie wywołania me-tody. To system zdecyduje, kiedy opera-cja ta zostanie wykonana. A jej wykona-nie oznacza wywołanie przez system me-tody onDraw().

Drobna uwaga. Metoda invalidate() może zostać wywołana jedynie z głów-nego wątku aplikacji. Jeżeli chcemy za-żądać odrysowania widoku z jakie-goś innego wątku, należy skorzystać z postInvalidate().

Mamy już własną klasę widoku, teraz na-leży tylko dodać aktywność, która ten wi-dok wykorzysta. Na szczęście twórcy sys-temu przewidzieli potrzebę obsługi nie-standardowych widoków w layoutach. Aby użyć własnej klasy, należy w pliku XML użyć pełnej nazwy klasy jako nazwy ele-mentu.

Możemy również definiować własne atrybuty. Aby nasza klasa widoku mo-gła być w ten sposób wykorzystywana, musimy zadeklarować konstruktor, któ-ry przyjmuje jako parametry obiekt klasy Context oraz AttributeSet. Ten drugi słu-ży do przekazywania atrybutów zdefinio-wanych w pliku XML. W tej sytuacji kod nowej aktywności sprowadza się do usta-wienia layoutu oraz obsługi dwóch przyci-sków. Gotowy layout przedstawiono na Li-stingu 16.

Przechowywanie danychDo szczęścia brakuje już nam tylko zapisu danych. Możemy tu niejako wyróżnić dwa aspekty problemu. Po pierwsze, zapisanie pli-ku ze zdjęciem. Po drugie, zapisanie danych o lokalizacji oraz powiązanie ich z konkret-nym zdjęciem.

Problem pierwszy jest banalnie prosty do rozwiązania. Android udostępnia stan-dardowe klasy wejścia/wyjścia z JavySE. W wypadku zapisu na kartę pamięci, musi-my tylko znać do niej ścieżkę oraz upewnić się, że karta jest podmontowana w syste-mie. Wywołanie Environment.getExternalStorageDirectory() zwraca nam obiekt File zawierający właśnie tę ścieżkę. Nato-miast stan karty możemy pozyskać za po-mocą metody Environment.getExternalStorageState(). Dalej możemy postępo-wać tak, jak byśmy pisali przy użyciu desk-topowej Javy.

Drugi aspekt jest nieco bardziej intere-sujący, ze względu na to, że nie tylko mu-simy zapisać dane o lokalizacji, ale rów-nież skojarzyć je z konkretnym zdjęciem.

Listing 16. Layout dla ekranu zatwierdzania zdjęcia.

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

<RelativeLayout

android:layout_width="fill_parent"

android:layout_height="fill_parent"

xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/RelativeLayout"

>

<Button

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_alignParentTop="true"

android:id="@+id/buttonSave"

android:text="@string/photoSave"

></Button>

<Button

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentTop="true"

android:layout_alignParentRight="true"

android:text="@string/photoDiscard"

android:id="@+id/buttonDelete"

></Button>

<org.sdjournal.galbum.viewer.FullscreenImageView

android:layout_below="@id/buttonDelete"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:id="@+id/photoView"

android:focusable="true"

></org.sdjournal.galbum.viewer.FullscreenImageView>

</RelativeLayout>

Page 111: SDJ Extra 34 Biblia

110

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 111

Można by tu wymyślić multum potencjal-nych rozwiązań, jednak my nie będziemy odkrywać na nowo Ameryki. Twórcy An-droida również postąpili podobnie i sys-tem ten udostępnia mechanizm zapisy-wania danych poprzez zastosowanie bazy danych opartej na SQLite. Skorzystamy z tego właśnie rozwiązania. Nasza baza da-nych będzie zawierać zaledwie jedną ta-belę, w której dla każdego zdjęcia zapisa-ne będą: identyfikator (klucz główny), na-zwa pliku, tytuł, szerokość i długość geo-graficzna oraz wysokość.

Do obsługi naszej bazy utworzymy dwie klasy pomocnicze. PhotoManager będzie za-rządzał bazą danych oraz oferował możli-wość dodawania i pobierania zdjęć z bazy. Natomiast klasa Photo będzie reprezentowa-ła konkretne zdjęcie oraz zajmowała się zapi-sem i odczytem danych konkretnego zdjęcia z systemu plików.

Klasa Photo nie wymaga specjalnego ko-mentarza. Posiada pola do przechowywa-nia pozycji geograficznej, nazwy pliku od-powiadającego zdjęciu oraz samych danych zdjęcia. Udostępnia też metodę do pobra-nia obiektu typu Bitmap, który można wy-świetlić na ekranie.

Ze względu na ograniczone zasoby pa-mięci, bitmapa oraz dane zdjęcia są łado-wane tylko na żądanie. Ponadto klasa udo-stępnia metody do zwolnienia zasobów zaj-mowanych przez te dane. Może wyrażenie zwolnienie zasobów jest nieco nie na miej-scu, w końcu mamy do czynienia z Javą. Jednak przypisanie tym polom wartości null zapewni, że odśmiecacz będzie mógł odzyskać pamięć przez nie zajmowaną (oczywiście pod warunkiem, że nie korzy-stamy z nich w innym miejscu programu). Należy pamiętać, że zdjęcie w swojej orygi-nalnej rozdzielczości zajmuje sporo pamię-ci. Na Listingu 17 znajduje się kod źródło-wy metod odpowiadających za zapis i od-czyt zdjęcia.

Klasa PhotoManager jest zdecydowanie ciekawsza, gdyż zajmuje się obsługą bazy danych. Dla uproszczenia wszystkie me-tody są statyczne, a metoda init() wy-woływana jest na starcie naszej aplika-cji i zajmuje się inicjalizacją bazy. Nato-miast release() zamyka połączenie z ba-zą i jest wywoływane przy wyjściu z aplika-cji. Do tworzenia oraz pozyskiwania obiek-tu reprezentującego bazę danych posługuje-my się klasą pomocniczą DatabaseHelper, która dziedziczy po SQLiteOpenHelper. Jej kod jest przedstawiony na Listingu 18. Wykorzystujemy przesłonięte metody onCreate() oraz onUpgrade() do wyko-nywania akcji w momencie tworzenia ba-zy danych oraz jej aktualizacji. Stosujemy uproszczony jednoargumentowy konstruk-tor, który z kolei wywołuje konstruktor kla-

Listing 17. Zapis zdjęcia na kartę pamięci oraz jego wczytywanie i tworzenie obiektu Bitmap

public class Photo {

private String mFilename;

private String mTitle;

private Bitmap mImage;

private byte[] mJpgData;

boolean saveToFile(String filename) {

boolean success = false;

if (mJpgData != null) {

success = true;

String sdcardState = Environment.getExternalStorageState();

if (sdcardState.equals(Environment.MEDIA_MOUNTED)) {

try {

File sdCardDir = Environment.getExternalStorageDirectory();

File photoFile = new File(sdCardDir, filename);

FileOutputStream fos = new FileOutputStream(photoFile);

BufferedOutputStream bos = new BufferedOutputStream(fos);

bos.write(mJpgData);

} catch (IOException e) {

e.printStackTrace();

success = false;

}

} else {

success = false;

}

} else {

success = false;

}

return success;

}

boolean saveToFile() {

if (mFilename != null) {

return saveToFile(mFilename);

} else {

return false;

}

}

public void initBitmap() {

if (mImage == null) {

if (mJpgData != null) {

mImage = BitmapFactory.decodeByteArray(mJpgData, 0,

mJpgData.length);

} else {

String sdcardState = Environment.getExternalStorageState();

if (sdcardState.equals(Environment.MEDIA_MOUNTED)) {

File sdCardDir = Environment.getExternalStorageDirectory();

File photoFile = new File(sdCardDir, mFilename);

mImage = BitmapFactory.decodeFile(photoFile.getAbsolutePath());

}

}

}

}

}

Page 112: SDJ Extra 34 Biblia

112

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 113

sy nadrzędnej z nazwą oraz numerem wer-sji bazy.

Wspomniany numer wersji możemy wy-korzystać w sytuacji, gdy nowa wersja na-

szej aplikacji zmienia strukturę bazy da-nych i użytkownik starej wersji ją zaktu-alizuje. Wtedy mamy możliwość dosto-sowania struktury bez utraty danych. W wypadku naszej aplikacji nas to nie inte-resuje i przy aktualizacji kasujemy naszą jedyną tabelę i wywołujemy onCreate(). Aby otworzyć (lub stworzyć, jeśli nie ist-nieje) bazę danych, korzystamy z metody getWritableDatabase() naszej klasy po-mocniczej.

W rezultacie dostaniemy obiekt klasy SQLiteDatabase, który udostępnia metody do operowania na bazie, takie jak query() oraz insert().

Funkcjonalność związana z ładowa-niem oraz zapisywaniem danych zdjęć do bazy zaimplementowana jest w me-todach loadPhoto() (Listing 19) oraz storePhoto() (Listing 20). Natomiast 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 ma pobrać, zwraca natomiast obiekt kla-sy Photo zainicjalizowany wartościami po-branymi z bazy. Do pozyskania danych uży-wana jest wspomniana wcześniej meto-da SQLiteDatabase.query(). W związku z tym, że nasze zapytanie jest bardzo pro-ste (chcemy pobrać zawartość całej tabeli), to jako parametry przekazujemy tylko na-zwę tabeli oraz listę nazw kolumn. Pozosta-łe parametry mają wartość null, co ozna-cza, że nie korzystamy z dodatkowej funk-cjonalności, jaką oferuje SQL (np. klauzula where, sortowanie). Tak naprawdę nie mu-simy nawet podawać listy kolumn, w ta-kim wypadku zwrócona zostałaby zawar-tość wszystkich.

W normalnych warunkach raczej nie powinno się pobierać całej zawartości ta-beli, jednak baza danych dla naszej przy-kładowej aplikacji nie będzie miała wiel-kich rozmiarów. Metoda query() zwraca obiekt typu Cursor (zwany dalej po pro-stu kursorem), który przechowuje wszyst-kie zwrócone wiersze tabeli oraz udostęp-nia interfejs do pobierania danych z dowol-nego wiersza.

Aby nie powtarzać tego samego zapyta-nia za każdym razem, zapamiętujemy wy-nik do dalszego wykorzystania. Aby móc pobierać dane z danego wiersza, należy przy pomocy metody Cursor.moveToPosition(int) ustawić kursor na odpowied-niej pozycji.

Następnie możemy skorzystać z jed-nej z metod get, które zwracają dane róż-nych typów (np. getString(), getInt(), getDouble() itd.). Jako parametr meto-dy te przyjmują indeks kolumny, z której chcemy pobrać dane. Z kolei do pobrania tego indeksu dla kolumny o podanej na-

Listing 18. Klasa pomocnicza korzystająca z SQLiteOpenHelper private static class DatabaseHelper extends SQLiteOpenHelper {

DatabaseHelper(Context context) {

super(context, DATABASE_NAME, null, DATABASE_VERSION);

}

public void onCreate(SQLiteDatabase db) {

db.execSQL("CREATE TABLE " + PhotoManager.PHOTOS_TABLE_NAME + " ("

+ PhotoManager.COLNAME_ID + " INTEGER PRIMARY KEY,"

+ PhotoManager.COLNAME_TITLE + " TEXT,"

+ PhotoManager.COLNAME_FILE_NAME + " TEXT,"

+ PhotoManager.COLNAME_ALTITUDE + " NUMERIC,"

+ PhotoManager.COLNAME_LATITUDE + " NUMERIC,"

+ PhotoManager.COLNAME_LONGITUDE + " NUMERIC"

+ ");");

}

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

db.execSQL("DROP TABLE IF EXISTS " + PhotoManager.PHOTOS_TABLE_NAME);

onCreate(db);

}

}

Listing 19. Metoda loadPhoto() służąca do odczytu danych z bazy

private static SQLiteDatabase mDb;

private static Cursor mPhotoData;

private static boolean mPhotoDataInvalid;

public static Photo loadPhoto(int index) {

if (mPhotoData == null) {

mPhotoData = mDb.query(PHOTOS_TABLE_NAME, new String[] {

COLNAME_ID, COLNAME_FILE_NAME, COLNAME_TITLE,

COLNAME_LATITUDE, COLNAME_LONGITUDE }, null, null, null,

null, null);

}

if (mPhotoDataInvalid) {

synchronized (mDb) {

mPhotoData.requery();

mPhotoDataInvalid = false;

}

}

Photo photo = null;

boolean isValid = mPhotoData.moveToPosition(index);

if (isValid) {

String filename = mPhotoData.getString(mPhotoData

.getColumnIndexOrThrow(PhotoManager.COLNAME_FILE_NAME));

String title = mPhotoData.getString(mPhotoData

.getColumnIndexOrThrow(PhotoManager.COLNAME_TITLE));

int id = mPhotoData.getInt(mPhotoData

.getColumnIndexOrThrow(PhotoManager.COLNAME_ID));

double lon = mPhotoData.getDouble(mPhotoData

.getColumnIndexOrThrow(PhotoManager.COLNAME_LONGITUDE));

double lat = mPhotoData.getDouble(mPhotoData

.getColumnIndexOrThrow(PhotoManager.COLNAME_LATITUDE));

photo = new Photo();

photo.setFilename(filename);

photo.setTitle(title);

photo.setId(id);

photo.setLatitude(lat);

photo.setLongitude(lon);

}

return photo;

}

Page 113: SDJ Extra 34 Biblia

112

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 113

zwie służy metoda Cursor.getColumnIndexOrThrow().

Dodawaniem wpisów do bazy danych zaj-muje się metoda storePhoto(). Robi ona użytek z metody SQLiteDatabase.insert(), której należy jako parametry podać nazwę tabeli, nazwę kolumny, której zostanie przy-pisana wartość NULL w wypadku dodawa-nia pustego wiersza (ignorujemy ten para-metr, przekazując null) oraz wartości ko-lumn do dodania.

Wynikiem tej metody jest indeks nowe-go wiersza lub -1 w wypadku niepowodze-nia operacji. Dane do dodania przekazujemy w obiekcie typu ContentValues, który jest zbiorem danych w postaci klucz-wartość. Dane dodajemy, korzystając z jednej z prze-ciążonych metod put.

Wspomniałem wcześniej, że obiekt Cursor zawierający rezultat zapytania o zawartość tabeli zapamiętamy do ponow-nego wykorzystania. Jednak jego zawar-tość deaktualizuje się w momencie doda-nia nowego wiersza do bazy. Nie oznacza to wcale, że musimy ponownie tworzyć zapytanie. Możemy odświeżyć dane, wy-wołując metodę Cursor.requery(), któ-ra wykona to samo zapytanie, które zo-stało użyte do utworzenia naszego kurso-ra. Taka operacja jest szybsza niż wywo-łanie query(), które tworzy nowy kursor. Jednak my nie będziemy aktualizować go natychmiast, tylko dopiero w momencie, kiedy zajdzie taka potrzeba, czyli w me-todzie loadPhoto(). Jednak aby nieaktu-alne dane nie zajmowały pamięci, deak-tywujemy kursor metodą deactivate() i zaznaczamy ten fakt, ustawiając pole ty-pu boolean. W stanie nieaktywnym wszel-kie operacje na kursorze zakończą się nie-powodzeniem aż do momentu wywołania requery().

Ostatnią metodą operującą na bazie da-nych jest photoCount(), która zwraca liczbę zapisanych zdjęć. Najprostszym sposobem na zaimplementowanie tej funkcji jest wy-wołanie na kursorze metody getCount(), która zwraca liczbę jego wierszy. Nie jest to jednak najlepsze rozwiązanie, gdyż nasz kursor może być w tym momencie nieak-tywny, a my chcemy go aktywować dopiero 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-rowanej przez androidowe API. Osobom zaznajomionym z SQL na pewno nieob-ca jest funkcja count(), która służy wła-śnie do zliczania wierszy. Dla naszej bazy, zapytanie SQL z jej użyciem wyglądałoby następująco:

select count(id) from photos

Wynikiem wykonania takiego zapytania jest pojedyncza wartość (czyli tabelę za-wierającą jeden wiersz i jedną kolumnę) będąca liczbą wierszy w naszej tabeli. Te-go typu zapytania możemy zoptymalizo-wać poprzez skompilowanie ich do posta-ci obiektów klasy SQLiteStatement, któ-re możemy zachować do ponownego wy-korzystania.

Wykonanie tak skompilowanego zapyta-nia następuje w momencie wykonania me-tody simpleQueryForLong(), która zwra-ca rezultat w postaci numerycznej (sim-pleQueryForString() zwraca wartość tek-stową).

Pozostałe metody klasy PhotoManager po-wstały na użytek funkcji zatwierdzania no-wo wykonanego zdjęcia. Jest to prymitywny sposób przekazania danych zdjęcia z aktyw-ności kamery do aktywności podglądu i za-twierdzania nowego zdjęcia.

Ta pierwsza dodaje dane obraz-ka jpg oraz lokalizacji poprzez meto-dę addPendingPhoto(), która utwo-rzy dla nich obiekt Photo i zacho-wa go dopóki nie zostanie wywoła-na metoda discardPendingPhoto() lub storePendingPhoto(). Docelowa aktyw-ność używa metody getPendingPhoto() , aby pobrać nowe zdjęcie i pozyskać z niego bitmapę do wyświetlenia na ekranie.

Wyświetlanie mapy za pomocą Google MapsAndroid, będąc produktem Google, ma do-skonałe wsparcie dla jednej z najpopular-niejszych usług tego giganta, czyli Google Maps. Pakiet com.google.android.maps zawiera klasy, przy użyciu których moż-na korzystać z tego serwisu. Jednak za-nim zagłębimy się w przykłady użycia tych klas, musimy wykonać kilka dodat-kowych czynności. Po pierwsze, od wersji 1.5 systemu, Google Maps API jest osobną biblioteką i korzystanie z niej wymaga do-datkowego wpisu w manifeście. Otwiera-my AndroidManifest.xml na zakładce Ap-plication i w sekcji Application Nodes do-dajemy nowy element typu Uses Libra-ry i ustawiamy jego wartość na jedyną z dostępnych opcji, czyli com.google.andro-id.maps. Ponadto we właściwościach pro-jektu, w sekcji Android, wybieramy Go-ogle APIs w okienku z wyborem platfor-my docelowej. Druga kwestia wiąże się z korzystaniem z samej usługi. Podobnie jak w wypadku aplikacji webowej, potrzebuje-my klucza API dla naszej aplikacji. Różni-ca jest taka, że w przypadku aplikacji an-droidowej klucz jest generowany dla cer-tyfikatu, którym jest podpisana nasza aplikacja. Proces generowania klucza jest szczegółowo opisany na stronie Google

Listing 20. Metody do zapisu zdjęcia w bazie i sprawdzenia ich liczby

public static void storePhoto(Photo photo) {

ContentValues values = new ContentValues(5);

values.put(COLNAME_FILE_NAME, photo.getFilename());

values.put(COLNAME_TITLE, photo.getTitle());

values.put(COLNAME_LATITUDE, photo.getLatitude());

values.put(COLNAME_LONGITUDE, photo.getLongitude());

values.put(COLNAME_ALTITUDE, photo.getAltitude());

long newRow = mDb.insert(PHOTOS_TABLE_NAME, null, values);

if (newRow == -1) {

// wstawienie nowego rekordu nie powiodło się

}

if (mPhotoData != null) {

synchronized (mDb) {

mPhotoData.deactivate();

mPhotoDataInvalid = false;

}

}

}

public static int photoCount() {

if (countStatement == null) {

countStatement = mDb.compileStatement("select count(" + COLNAME_ID

+ ") from " + PHOTOS_TABLE_NAME);

}

return (int) countStatement.simpleQueryForLong();

}

Page 114: SDJ Extra 34 Biblia

114

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 115

(http://code.google.com/intl/pl/android/add-ons/google-apis/mapkey.html). Ostatnią rze-czą jest ustawienie uprawnień dla korzy-

stania z Internetu z poziomu aplikacji. W sposób opisany już we wcześniejszej czę-ści artykułu dodajemy odpowiedni wpis w

manifeście, z wartością android.permission.INTERNET.

Możemy wreszcie rozpocząć pracę nad wykorzystaniem map w naszej aplikacji. Chcemy, aby po kliknięciu w przycisk Gal-lery oczom użytkownika ukazała się mapa z markerami oznaczającymi miejsca zro-bienia zdjęć. Mapa wycentrowana będzie na ostatnio wykonanym zdjęciu. Tworzy-my nową klasę aktywności, jednak tym ra-zem dziedziczymy po MapActivity, któ-ra jest częścią biblioteki i zajmuje się ma-ło nas interesującymi szczegółami związa-nymi z obsługą usługi Google Maps. Dru-gą ważną klasą pomocniczą udostępnianą przez bibliotekę jest MapView. Jak sama na-zwa wskazuje, służy do wyświetlania mapy. Ponadto,mocno polega na wnętrznościach MapActivity, i w rezultacie może być uży-wana wyłącznie w parze z tą klasą. Wyko-rzystanie obu tych klas przedstawione jest na Listingu 21. Tym razem odpuścimy so-bie definiowanie layoutu w pliku XML i zrobimy to w kodzie. Tworzymy obiekt ty-pu MapView, podając konstruktorowi naszą aktywność oraz klucz API. Następnie włą-czamy dla niego obsługę kliknięć poprzez wywołanie metody setClickable(true). Jest to konieczne, aby użytkownik miał możliwość przesuwania mapy.

Następnie ustawiamy widok dla ak-tywności, przekazując go do metody setContentView(). Po tych czynnościach uruchomienie naszej nowej aktywności spowoduje wyświetlenie mapy, którą użyt-kownik może przesuwać.

Niestety, domyślna skala mapy oraz brak możliwości jej zmiany oraz fakt, że najpraw-dopodobniej jest ona wycentrowana gdzieś daleko, raczej nie zrobi pozytywnego wraże-nia. I tu na ratunek przybywa klasa o jakże adekwatnej nazwie MapController. Obiekt tego typu pozyskujemy z instancji kla-sy MapView poprzez wywołanie jej metody getController().

A kontroler udostępnia tak przydatne me-tody jak:

• setZoom(int) do ustawiania skali (za-kres od 1 do 21 włącznie);

• zoomIn() oraz zoomOut() do płynnej zmiany skali o jeden stopień;

• setCenter(GeoPoint) do wycentrowa-nia mapy na danym punkcie.

Tym samym możemy zrealizować jed-ną z zaplanowanych funkcji, tzn. wycen-trować mapę na lokalizacji najświeższe-go zdjęcia. Sytuację, w której nie mamy w bazie ani jednego zdjęcia ignorujemy, jako niegodną naszej uwagi. Należałoby się zastanowić, czy w takiej sytuacji war-to w ogóle dawać użytkownikowi możli-wość uruchomienia mapy. W końcu po co

Listing 21. Aktywność korzystająca z mapy

public class MapGalleryActivity extends MapActivity {

private MapController mMapController;

private final static String MAPS_API_KEY = "TWÓJ KLUCZ DO MAPS API";

protected void onCreate(Bundle icicle) {

super.onCreate(icicle);

MapView mapView = new MapView(this, MAPS_API_KEY);

mapView.setClickable(true);

setContentView(mapView);

int c = PhotoManager.photoCount();

Photo lastPhoto = PhotoManager.loadPhoto(c -1);

GeoPoint lastPoint = new GeoPoint(

(int) (lastPhoto.getLatitude() * 1e6),

(int) (lastPhoto.getLongitude() * 1e6));

mMapController = mapView.getController();

mMapController.setCenter(lastPoint);

mMapController.setZoom(16);

mapView.setBuiltInZoomControls(true);

Drawable bitmapDrawable = getResources().getDrawable(R.drawable.photo_

icon);

PhotoOverlay overlay = new PhotoOverlay(bitmapDrawable);

overlay.setOnFocusChangeListener(new OnFocusChangeListener() {

public void onFocusChanged(ItemizedOverlay overlay, OverlayItem

newFocus) {

if (newFocus != null) {

mMapController.animateTo(newFocus.getPoint());

}

}

});

overlay.setDoubleTapListener(new PhotoOverlay.DoubleTapListener() {

public void onDoubleTap(int index) {

lauchPhotoView(index);

}

});

mapView.getOverlays().add(overlay);

}

protected boolean isRouteDisplayed() {

return false;

}

private void lauchPhotoView(int photoIndex) {

ComponentName activityName = new ComponentName(

"org.sdjournal.galbum",

"org.sdjournal.galbum.PhotoViewActivity");

Intent intent = new Intent();

intent.setComponent(activityName);

intent.putExtra(PhotoViewActivity.INTENT_EXTRA_PHOTO_TO_VIEW, photoIndex);

startActivity(intent);

}

}

Page 115: SDJ Extra 34 Biblia

114

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 115

wchodzić do pustej galerii? Po zignorowa-niu tego niewygodnego problemu pobie-ramy zdjęcie i tworzymy obiekt GeoPoint odpowiadający jego pozycji, a następnie przekazujemy go do metody setCenter(). Warto w tym miejscu zwrócić uwagę na fakt, że Maps API do określania pozy-cji korzysta z mikrostopni, stąd mnoże-nie i konwersja przy tworzeniu obiektu GeoPoint. Zupełnie przy okazji ustawia-my skalę na jakąś sensowną wartość (np. 16, która nie tylko jest wartością sensow-ną, ale również elegancką). Oczywiście ustawienie skali na stałe bez możliwości jej zmiany przez użytkownika nie jest za-lecane. Należy dodać funkcję przybliża-nia i oddalania mapy. Można dodać wła-sne kontrolki, które do tego posłużą, gdyż MapView dziedziczy po ViewGroup, a więc może zawierać inne widoki (patrz ram-ka Android: GUI). My jednak skorzysta-my z wbudowanego mechanizmu ofero-wanego przez MapView poprzez wywoła-nie setBuiltInZoomControls() z parame-trem true.

Teraz wystarczy już tylko zaznaczyć na mapie miejsca wykonania zdjęć. MapView daje nam możliwość dodawania warstw zawierających dodatkowe informacje, któ-re są rysowane na mapie. Klasą bazową dla tego typu obiektów jest Overlay, jednak biblioteka oferuje dla naszej wygody kla-sy ItemizedOverlay oraz OverlayItem. Tworzymy nową klasę PhotoOverlay dzie-dziczącą po ItemizedOverlay, która bę-dzie reprezentowała warstwę zdjęć na mapie (Listing 22). Konstruktor tej kla-sy przyjmuje jako parametr obiekt ty-pu Drawable, który posłuży jako marker dla obiektów na tej warstwie. Wywołuje-my konstruktor klasy nadrzędnej, przeka-zując domyślny marker. Aby mógł on zo-stać poprawnie narysowany, należy okre-ślić jego granice. Możemy tego dokonać przy pomocy metod boundCenter() lub boundCenterBottom(). Pierwsza spowo-duje, że marker będzie wyśrodkowany na pozycji, którą reprezentuje.

W drugim przypadku marker będzie przypięty do pozycji środkiem dolnej kra-wędzi. Ostatnią operacją w konstruktorze jest wywołanie metody populate(), któ-ra uruchomi proces zapełniania warstwy danymi.

Mechanizm dodawania danych do war-stwy polega na dwóch metodach, któ-re musimy zaimplementować. Pierwszą z nich jest size(), która ma zwrócić liczbę elementów na warstwie, jest ona wywoły-wana przez populate() jeden raz, a jej re-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ę

tworzeniem obiektów zawartych w war-stwie. Jako parametr dostajemy numer ele-mentu do stworzenia. My ładujemy z ba-zy danych zdjęcie odpowiadające temu nu-merowi, a następnie pobieramy jego pozy-cję i zapisujemy w obiekcie GeoPoint. Lo-kalizację, tytuł oraz skrócony opis prze-kazujemy do konstruktora obiektu klasy OverlayItem, czyli naszego elementu na warstwie.

Mamy już gotową klasę, która umożliwi nam wyświetlenie zdjęć na mapie. Tworzy-my więc nowy obiekt klasy PhotoOverlay, przekazując mu domyślny marker. My uży-jemy do tego celu niewielką ikonkę przed-stawiającą zdjęcie, którą załadujemy ze spakowanych zasobów przy pomocy Resources.getDrawable(int). Gotowy obiekt warstwy może zostać dodany do mapy ce-lem wyświetlenia danych w nim zawartych. Aby to osiągnąć, należy pobrać listę warstw za pomocą metody MapView.getOverlays()

i dodać naszą warstwę, korzystając z meto-dy add().

Użytkownik może już zobaczyć na ma-pie miejsca wykonania zdjęć. Należy jesz-cze dać mu możliwość obejrzenia dowol-nego z nich. Zanim zaimplementujemy tę funkcję, proponuję dodać jeszcze jeden, aczkolwiek miły dla użytkownika szcze-gół. Sprawimy, że kiedy wskaże on któryś z markerów, widok mapy płynnie się na nim wyśrodkuje. Do tego celu wykorzystamy interfejs OnFocusChangeListener zawar-ty w klasie ItemizedOverlay. Implemen-tując jego metodę onFocusChanged() oraz rejestrując taki obiekt metodą setOnFocusChangeListener(), będziemy informo-wani o fakcie zmiany aktywnego elementu warstwy. Taka zmiana następuje właśnie w momencie wskazania markera palcem. Ja-ko parametry dostaniemy obiekt warstwy oraz element, którego dotyczy zdarzenie. Pobieramy pozycję markera i przekazuje-

Listing 22. Klasa odpowiedzialna za obsługę warstwy danych ze zdjęciami

public class PhotoOverlay extends ItemizedOverlay<OverlayItem> {

public PhotoOverlay(Drawable defaultMarker) {

super(defaultMarker);

boundCenter(defaultMarker);

populate();

}

protected OverlayItem createItem(int i) {

Photo photo = PhotoManager.loadPhoto(i);

OverlayItem item = null;

if (photo != null) {

GeoPoint point = new GeoPoint((int) (photo.getLatitude() * 1e6),

(int) (photo.getLongitude() * 1e6));

String title = photo.getTitle();

String snippet = photo.getFilename();

item = new OverlayItem(point, title, snippet);

}

return item;

}

public int size() {

return PhotoManager.photoCount();

}

private int lastTap = -1;

public interface DoubleTapListener {

public void onDoubleTap(int index);

}

private DoubleTapListener tapActionListener;

public void setDoubleTapListener(DoubleTapListener l) {

tapActionListener = l;

}

protected boolean onTap(int index) {

if (index == lastTap) {

tapActionListener.onDoubleTap(index);

}

lastTap = index;

return true;

}

}

Page 116: SDJ Extra 34 Biblia

116

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 117

my ją do metody MapController.animateTo(), w wyniku czego oczom użytkowni-ka ukaże się animacja przejścia do nowe-go punktu.

Po tej krótkiej dygresji wracamy do głów-nego tematu, czyli wyświetlenia żądane-go zdjęcia. Aby móc zrealizować tę funk-cję, należy najpierw określić, w jaki spo-sób użytkownik ma poinformować apli-kację o swoich zamiarach. Skoro wskaza-nie markera powoduje zmianę focusa i wy-środkowanie na nim mapy, to załóżmy, że ponowienie tej akcji na aktualnym marke-rze spowoduje uruchomienie aktywności wyświetlającej to zdjęcie. W celu zaimple-mentowania takiego zachowania przesło-nimy metodę onTap(int) w naszej klasie PhotoOverlay. Dzięki temu będziemy in-formowani o fakcie tapnięcia przez użyt-kownika w marker. Parametr tej metody in-formuje nas, którego z elementów dotyczy akcja. Zapamiętujemy tę informację, i jeże-li następnym razem otrzymamy ten sam indeks co zapamiętany, to znaczy, że użyt-kownik dwa razy z rzędu wskazał ten sam marker. Jako że z poziomu PhotoOverlay nie za bardzo mamy możliwość wywoła-nia nowej aktywności (a przynajmniej nie w naturalny sposób), to dodajemy mecha-

nizm informowania świata zewnętrznego o zajściu podwójnego tapnięcia. Do tego ce-lu służy interfejs DoubleTapListener, któ-ry zawiera definicję jednej tylko metody, onDoubleTap(), która jako parametr otrzy-muje indeks elementu, któremu przyda-rzyło się interesujące nas zdarzenie. Z po-ziomu MapGalleryActivity, za pomo-cą metody setDoubleTapListener() reje-strujemy nowy listener, którego zadaniem jest uruchomienie nowej aktywności, któ-ra wyświetli żądane zdjęcie. Zajmuje się tym metoda lauchPhotoView(int).

Tym razem korzystamy z intencji do przekazania nowej aktywności numeru zdjęcia do wyświetlenia. Zajmuje się tym metoda Intent.putExtra(String, int). Tę dodatkową informację można odczy-tać za pomocą metody getIntExtra(), co też robi nasza nowa aktywność, PhotoViewActivity. O tej klasie nie będę się specjalnie rozpisywał. Jest bardzo po-dobna do PhotoInspectActivity, z kilko-ma drobnymi różnicami. Po pierwsze, nie wyświetla nic oprócz zdjęcia, a ponadto wy-korzystuje tryb pełnoekranowy. Efekt ten osiągamy dzięki ustawieniu odpowiedniej flagi dla okna aktywności, poprzez wywoła-nie getWindow().setFlags() z odpowied-

nimi parametrami (Listing 23). Po drugie, zdjęcie jest pobierane z PhotoManagera na podstawie indeksu przekazanego w inten-cji. Po trzecie, posłuży nam do nieco bliż-szego poznania mechanizmu zmian konfi-guracji podczas działania aplikacji.

Wykrywanie orientacji telefonu Dla wygody użytkownika chcielibyśmy, aby aktywność wyświetlająca zdjęcie dostoso-wywała się do orientacji ekranu. Jak wspo-mniałem we wcześniejszej części artykułu, w wypadku zmiany konfiguracji (np. orien-tacji ekranu) aktywność jest restartowa-na. Takie zachowanie nam nie odpowiada, gdyż ponowne ładowanie zdjęcia jest dość kosztowne.

Ponadto chcemy, aby do określenia orientacji został użyty wbudowany w te-lefon sensor. Zapewne niektórych zdziwi, jak prosto osiągnąć taki efekt. Jedyne, co musimy zrobić, to zmodyfikować w ma-nifeście kilka ustawień dla naszej aktyw-ności. Wybieramy w Application Nodes ak-tywność i w polu Screen orientation wybie-ramy sensor, dzięki czemu system automa-tycznie wykryje zmiany położenia telefo-nu. Natomiast aby aktywność nie była re-startowana podczas zmiany konfiguracji, musimy zaznaczyć odpowiednią wartość w polu Config changes. W ten sposób in-formujemy system, że aktywność sama ob-służy zmianę konfiguracji. My zaznaczamy orientation oraz keyboardHidden. Ta druga wartość oznacza fakt wysunięcia lub wsu-nięcia klawiatury telefonu.

Tym sposobem osiągnęliśmy zamierzo-ny cel, jednak szkoda byłoby na tym po-przestać i zmarnować doskonałą szansę żeby się jeszcze trochę pobawić. Przecież w manifeście zaznaczyliśmy, że sami ob-służymy zmiany konfiguracji, a nie doda-liśmy nawet jednej linijki kodu. Czas na-prawić ten niewybaczalny błąd. Zacznij-my od kwestii wykrywania zmian konfigu-racji. W tym celu należy przeciążyć meto-dę Activity.onConfigurationChanged(), której parametr zawiera nową konfigurację w postaci obiektu Configuration. Bieżący stan możemy odczytać z publicznych pól 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-łane tylko dla zmian, które zaznaczone są w manifeście. Po drugie, należy pamiętać o przekazaniu informacji wyżej poprzez wywołanie super. onConfigurationCha

nged().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-

Listing 23. Klasa odpowiedzialna za wyświetlenie pełnoekranowego widoku zdjęcia

public class PhotoViewActivity extends Activity {

public final static String INTENT_EXTRA_PHOTO_TO_VIEW = "photoIdx";

private Photo photo;

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_NO_TITLE);

getWindow().setFlags(

WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN

);

FullscreenImageView photoView = new FullscreenImageView(this);

photoView.setFocusable(true);

Intent intent = getIntent();

if (intent != null) {

int photoIdx = intent.getIntExtra(INTENT_EXTRA_PHOTO_TO_VIEW, -1);

if (photoIdx != -1) {

photo = PhotoManager.loadPhoto(photoIdx);

if (photo.getBitmap() != null) {

photoView.setImage(photo.getBitmap());

}

}

}

setContentView(photoView);

}

protected void onDestroy() {

super.onDestroy();

if (photo != null) {

photo.releaseBitmap();

}

}

}

Page 117: SDJ Extra 34 Biblia

116

Programowanie Android

SDJ Extra 34 Biblia

Android na przykładzie: geotagging zdjęć

www.sdjournal.org 117

sy OrientationEventListener z pakietu android.view.

Korzysta ona z wbudowanego sensora i udostępnia cztery metody:

• canDetectOrientation() powie nam, czy wykrycie orientacji jest możliwe;

• enable() włączy monitorowanie senso-ra w celu informowania o zmianie orien-tacji;

• disable() wyłączy powyższą funkcję;• abstrakcyjna metoda onOrientationC

hanged(int) poinformuje nas o zmia-nie, przekazując w argumencie orienta-cję w stopniach, gdzie 0 oznacza natu-ralne położenie telefonu, 90 to wychy-

lenie w prawo do poziomu itd. W wy-padku, gdy nie można określić orien-tacji z powodu położenia telefonu, to przekazane zostanie ORIENTATION _

UNKNOWN.

Wiemy już, jak wykrywać fizyczne od-chylenie telefonu, jak natomiast użyć tej informacji do ręcznej zmiany orienta-cji ekranu? W tym celu możemy posłu-żyć się metodą Activity.setRequestedOrientation(int), przekazując jako para-metr jedną ze stałych zdefiniowanych w ActivityInfo. Analogicznie, do sprawdze-nia aktualnej orientacji służy getRequestedOrientation(). Przykład użycia opisa-

nego powyżej mechanizmu przedstawia Listing 24.

PodsumowanieEfekt finalny, czyli nasza przykładowa apli-kacja, nie jest może specjalnie imponują-ca, jednak przy jej tworzeniu wykorzystali-śmy wiele z ciekawych funkcji oferowanych przez Androida. Puryści mogą być nieco zniesmaczeni uproszczeniami, zaniedba-niami i ewidentnymi skrótami w kodzie, które nie powinny mieć miejsca w przypad-ku aplikacji przeznaczonej do dystrybucji. Jednak celem artykułu nie było napisanie aplikacji ani popisywanie się znajomością inżynierii oprogramowania, a jedynie po-służenie się nią jako pretekstem do zabawy i poznawania ciekawie zapowiadającego się systemu operacyjnego.

Autor tego artykułu, będąc mocno zako-rzeniony w realiach urządzeń mobilnych, często podkreśla, jak to trzeba oszczędzać zasoby, bo tak mało ich jest do dyspozycji na telefonach komórkowych. Może to spra-wiać wrażenie, że Android jest w jakimś stopniu upośledzony. Nic bardziej myl-nego, warto spojrzeć na Androida z szer-szej perspektywy, której tylko częścią jest świat telefonów komórkowych. Jest to no-woczesny system operacyjny oparty na Li-nuksie z jądrem serii 2.6. Do tego jego źró-dła są otwarte. Ten fakt zaoszczędził auto-rowi sporo czasu przy pisaniu kodu obsłu-gującego kamerę.

Lektura źródeł systemowej aplikacji Ca-mera była bardzo pouczająca. Wszystko wskazuje na to, że producenci sprzętu kom-puterowego również dostrzegają potencjał 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-to przyglądać się Androidowi z zaintere-sowaniem.

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.

Listing 24. Przykład użycia OrientationEventListener do wykrycia wychylenia telefonu i zmiany orientacji ekranu

OrientationEventListener orientationListener;

protected void onCreate(Bundle savedInstanceState) {

// inicjalizacja jak w PhotoViewActivity

setContentView(photoView);

orientationListener = new OrientationEventListener(this) {

public void onOrientationChanged(int orientation) {

int curOrient = getRequestedOrientation();

if (curOrient == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||

curOrient == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {

if (orientation > 60 && orientation < 315) {

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_

LANDSCAPE);

}

}

if (curOrient == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ||

curOrient == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {

if (orientation > 315 || orientation < 60) {

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_

PORTRAIT);

}

}

}

};

}

protected void onStart() {

super.onStart();

orientationListener.enable();

}

protected void onStop() {

super.onStop();

orientationListener.disable();

}

PIOTR BUŁAPracuje jako programista Java w firmie Game-lion, wchodzącej w skład Grupy BLStream. Jego pasją są gry komputerowe, a specjalizacją Java-ME, którą to technologię wykorzystuje do tworze-nia gier na telefony komórkowe. Kontakt z autorem: [email protected]

Page 118: SDJ Extra 34 Biblia

118

Programowanie Android

SDJ Extra 34 Biblia

Google Maps

www.sdjournal.org 119

Naszym celem jest napisanie wła-snej aplikacji działającej w oparciu o platformę Android, w której wy-

korzystamy usługę Google Maps. Zakładamy, że nasza aplikacja będzie umożliwiała wyko-nywanie następujących operacji:

• wyświetlanie map Google;• funkcja zoom (powiększanie i zmniejsza-

nie map);• zmiana trybu wyświetlania: normalny,

satelita, ulice;• automatyczne wyświetlenie/przejście do

określonej lokalizacji;• wyświetlenie komunikatu z informacją

o adresie klikniętego punktu na mapie.

Zakładam również, że tworzymy aplikację na platformę Windows. Do rozpoczęcia pro-gramowania potrzebujemy tak naprawdę tyl-ko trzech komponentów:

• JAVA Software Development Kit (SDK) – opisane przeze mnie w poprzednim artykule;

• Android Software Development Kit (SDK) – środowisko umożliwiające tworzenie aplikacji na platformę Android. Udostępnia także narzędzie umożliwiające uruchamia-

nie i testowanie utworzonych aplikacji, a także obszerną i niezwykle pomocną doku-mentację. Darmowe środowisko pobierze-my ze strony: http://developer.android.com/sdk/ (zakładka Download);

• dowolnego środowiska deweloperskiego, np. Eclipse, które możemy pobrać za darmo ze strony: http://www.eclipse.org/ (z zakładki Download wybieramy Eclipse IDE for Java Developers). Oczywiście moglibyśmy pisać kod naszej aplikacji w dowolnym edytorze tekstowym (np. systemowy Notatnik), a na-stępnie uruchamiać z poziomu wiersza po-leceń, lecz chyba nie muszę nikomu tłuma-czyć, jak bardzo katorżnicza byłaby to praca.

Instalacja i konfiguracjaPo ściągnięciu Android SDK należy wypako-wać archiwum. Domyślnie archiwum wypa-kowuje się do katalogu o nazwie w konwen-cji android_sdk_<platform>_<release>, gdzie platform oznacza platformę np. Windows, a release to wersja SDK. Ścieżkę z wypakowa-nym Android SDK musimy dodać do zmien-nej systemowej Path, w tym celu:

• klikamy Mój komputer prawym przyci-skiem myszy i wybieramy Właściwości (Properties);

• przechodzimy na zakładkę Zaawansowa-ne (Advanced);

• klikamy Zmienne Środowiskowe (Environ-ment Variables);

• dwukrotnie klikamy w zmienną syste-mową Path;

• w nowym oknie, pośredniku dopisuje-my ścieżkę do katalogu Tools w folderze z SDK, w moim przypadku jest to: D:\android-sdk-windows-1.5_r2\tools.

Pamiętajmy, że jeśli w przyszłości zmienimy lo-kalizację SDK , musimy także wprowadzić od-powiednią zmianę do tej zmiennej systemowej. Kolejnym krokiem jest konfiguracja środowiska Eclipse do działania z Android SDK. W moim przypadku jest to Eclipse 3.4.2 Ganymode:

• uruchamiamy środowisko Eclipse;• z menu Help wybieramy opcję Software

Updates...;• przechodzimy na zakładkę Available So-

ftware;• klikamy przycisk Add Site...;• wpisujemy adres: https://dl-ssl.google.com/

android/eclipse/ (jeśli wystąpi problem z pobraniem aplikacji z tej lokalizacji, nale-ży zamienić https na http) i klikamy OK;

• zaznaczmy pole przy Developer Tools i klikamy Install;

• upewniamy się, że pola przy Android DDMS i Android Development Tools są zaznaczone i klikamy Next;

• akceptujemy warunki licencyjne i klika-my Finish;

• restartujemy środowisko Eclipse.

Po restarcie środowiska, z menu Window wy-bieramy Preferences, a następnie przechodzimy na zakładkę Android. W oknie wpisujemy ścież-kę do SDK (np.: D:\android-sdk-windows-1.5_r2) (Rysunek 1), a następnie klikamy Apply i OK.

Jak już pisałem wcześniej, w naszej aplika-cji chcemy wykorzystać usługę Google Maps. W tym celu musimy uzyskać darmowy tzw. klucz Google Maps API. W tym celu musimy wykonać następujące czynności:

• odnajdujemy plik debug.keystore, w moim przypadku znajduje się on w następują-

Google Android

W artykule „JME – MIDlet – Przelicznik walut na podstawie kursów NBP” opisuje architekturę JME oraz prezentuje sposób, w jaki można rozpocząć przygodę z programowaniem z wykorzystaniem tej technologii. Artykuł poświęcony jest platformie Android, na której można tworzyć zaawansowane aplikacje m.in. z wykorzystaniem Google Maps.

Dowiesz się:• Co to jest Google Android;• Jak tworzyć i uruchamiać programy na plat-

formie Android;• Jak programować z wykorzystaniem Google

Maps.

Powinieneś wiedzieć:• Podstawy języka Java;• Podstawy języka XML.

Poziom trudności

Programowanie z wykorzystaniem Google Maps

Page 119: SDJ Extra 34 Biblia

118

Programowanie Android

SDJ Extra 34 Biblia

Google Maps

www.sdjournal.org 119

cej lokalizacji: C:\Documents and Settings\user_name\.android, gdzie user_name to nazwa użytkownika systemowego.

• dla wygody kopiujemy ten plik np. do katalogu c:\

• uruchamiamy wiersz poleceń (cmd) i prze-chodzimy do katalogu, w którym znajdu-je się narzędzie keytool.exe (w folderze z zainstalowanym Java SDK), w moim przypadku jest to następująca lokalizacja: C:\Program Files\Java\jdk1.6.0_13\bin

• wykonujemy następujące polecenie:

keytool.exe -list -alias androiddebugkey

-keystore "C:\debug.keystore"

-storepass android -keypass android

• Na ekranie powinien zostać wyświetlo-ny klucz MD5, który kopiujemy.

• Przechodzimy na stronę http://code.google.com/intl/pl/android/maps-

api-signup.html, gdzie zaznaczeniem po-twierdzamy zapoznanie się z regulami-nem, a następnie wklejamy nasz klucz MD5 i klikamy Generate API key

• W nowym oknie pojawi się klucz, który wykorzystamy w dalszej części artykułu.

Jeśli wszystko przebiegło bezbłędnie, to w tym momencie mamy poprawnie skonfigu-rowane środowisko i możemy rozpocząć pi-sanie aplikacji na platformę Android.

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

• Project Name – nazwa projektu w Eclip-se, a także nazwa katalogu, w którym projekt będzie zapisany;

• Contents – lokalizacja katalogu projektu;• Build Target – docelowa wersja platfor-

my, dla której nasza aplikacja będzie kompilowana. W związku z tym, że bę-dziemy oprogramowywać Google Maps, wybieramy Google APIs (zauważmy, że automatycznie w polu Min SDK Version pojawia się wartość 3).

• Application Name – dowolna nazwa aplika-cji, która będzie wyświetlana w telefonie;

• Package Name – nazwa pakietu dla na-szej aplikacji. Nazwa pakietu musi być unikalna dla całej platformy Android. W naszym testowym przypadku wy-korzystamy umowną nazwę pakietu com.example.adroidgooglemaps;

• Create Activity – deklarujemy utworze-nie aktywności, czyli podstawowej klasy, która wykona dla nas zakładane funkcjo-nalności.

Po wypełnieniu wszystkich pól klika-my Finish. Projekt jest gotowy do użycia. W katalogu AndroidGoogleMaps\src\com\

example\androidgooglemaps powinien po-jawić się plik AndroidGoogleMaps.java, w którym powinna pojawić się deklaracja kla-

Rysunek 1. Konfiguracja Android SDK w Eclipse

Rysunek 2. Tworzenie projektu Android

Page 120: SDJ Extra 34 Biblia

120

Programowanie Android

SDJ Extra 34 Biblia

Google Maps

www.sdjournal.org 121

sy AndroidGoogleMaps bazującej na kla-sie Activity. Aplikacja może posiadać wie-le różnych aktywności (klasy bazujących na klasie Activity), ale w tym samym cza-sie użytkownicy wykorzystują tylko jedną z nich. Oprócz definicji klasy powinna poja-wić się także definicja metody onCreate(), która jest wołana przez system Android pod-czas uruchamiania aktywności.

Konfiguracja plików XMLW celu poprawnego wyświetlania Google Maps w naszej aplikacji musimy skonfiguro-wać plik AndroidManifest.xml znajdujący się w głównym katalogu naszej aplikacji. Musi-my umieścić w nim odwołanie do biblioteki com.google.android.maps, w tym celu dodaje-my element <uses-library>, a także zezwolić na łączenie z Internetem i pobieranie danych o lokalizacji android.permission.ACCESS_FI-NE_LOCATION, w tym celu dodajemy ele-ment <uses-permission>. Plik AndroidMani-fest.xml możemy edytować ręcznie np. w No-tatniku, lub też w środowisku Eclipse. W tym celu klikamy dwukrotnie na niego w oknie Package Explorer, a następnie definiujemy od-powiednie elementy w zakładkach Permissions i Application (obszar Application Nodes). Po zmianach plik AndroidManifest.xml powinien wyglądać tak jak na Listingu 1.

Drugim plikiem XML, który musimy zmienić w celu poprawnego wyświetlenia Google Maps w naszej aplikacji, jest plik ma-in.xml znajdujący się w katalogu res/layout. Ponownie możemy edytować go ręcznie lub otworzyć w Eclipse. W pliku tym musimy za-gnieździć element <com.google.android.maps.MapView> odpowiadający za wyświetlenie mapy wewnątrz elementu <RelativeLayout> odpowiadającego za rozmieszczenie elemen-tów w aplikacji. Plik ten powinien wyglądać tak jak na Listingu 2. Zwróćmy uwagę, że dla atrybutu android:apiKey należy wkleić otrzy-many ze strony Google klucz.

Wyświetlanie mapyW 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.layout.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 AndroidZanim jednak uruchomimy naszą, na razie bardzo prostą, aplikację, musimy najpierw zdefiniować Android Virtual Device (AVD).

Listing 1. Zawartość pliku AndroidManifest.xml

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.androidgooglemaps"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<uses-library android:name="com.google.android.maps" />

<activity android:name=".AndroidGoogleMaps"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_COARSE_

LOCATION" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"

/>

</manifest>

Listing 2. Zawartość pliku main.xml

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent">

<com.google.android.maps.MapView

android:id="@+id/mapView"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:enabled="true"

android:clickable="true"

android:apiKey="0TmVl5DkCEFnK2wWYo6YE_ygBU6NqcN53US5ocg"

/>

</RelativeLayout>

Rysunek 3. Google Maps w telefonie Android

Page 121: SDJ Extra 34 Biblia

120

Programowanie Android

SDJ Extra 34 Biblia

Google Maps

www.sdjournal.org 121

Jest to odpowiednik emulatora, na którym będziemy testować działanie naszej aplikacji. Możemy zdefiniować wiele obiektów AVD dla poszczególnych platform docelowych. W celu utworzenia AVD z menu Window wy-bieramy Android AVD Manager. W nowym oknie, w obszarze Create AVD wpisujemy na-zwę (Name), np. MapsAVD, wybieramy plat-formę docelową (Target Platform), w naszym przypadku jest to Google APIs 1.5 i klika-my Finish. Teraz możemy już uruchomić na-szą aplikację. W tym celu z menu Run wybie-ramy opcję Run lub klikamy [CTRL + F11], a następnie wybieramy tryb: Android Appli-cation. W moim przypadku zanim emulator wystartował zajęło to trochę czasu, więc trze-ba uzbroić się w cierpliwość.

W chwili obecnej nasza aplikacja poza wy-świetleniem mapy nie pozwala na nic więcej. W dalszej części artykułu zajmiemy się funkcjonal-nością zoom'u i zmianą trybu wyświetlania.

Zoom i zmiana trybu wyświetlaniaW najnowszej wersji Android SDK 1.5 w ce-lu uzyskania funkcjonalności zoom'u, czy-li przybliżania (powiększania) i oddalania (zmniejszania) mapy, nie trzeba już pisać wielu linii kodu (jak to było we wcześniejszej wersji SDK), które obsługiwałyby kliknięcia odpowiednich przycisków. Wystarczy jedynie na obiekcie typu MapView wywołać odpo-wiednią metodę setBuiltInZoomControl():

mapView = (MapView) findViewById(R.id.map

View);

mapView.setBuiltInZoomControls(true);

Obiekt typu MapView deklarujemy poza funk-cją onCreate(). Tutaj jedynie go definiujemy.

Załóżmy, że nasza aplikacja ma działać w ten sposób, że po kliknięciu przycisku 9 mapa ma włączać/wyłączać tryb satelity, zaś przycisk 8 będzie służył do włączania/wyłączania trybu ulic. W tym celu musi-my zdefiniować metodę onKeyDown(int

keyCode, KeyEvent event),w której sprawdzimy numer wciśniętego przycisku. Do włączania trybu satelity służy funkcja setSatellite(boolean), zaś do trybu ulic – funkcja setStreetView(boolean). W na-szym rozwiązaniu deklarujemy dwie zmien-ne globalne: isSatellite i isStreetView ty-pu boolean, które będą odpowiedzialne za rodzaj operacji włączenia/wyłączenia. Treść metody onKeyDown przedstawiłem na Listin-gu 3.Możemy już teraz sprawdzić nasze no-we dwie funkcjonalności. Po uruchomieniu emulatora (Run) klikamy przycisk 9 i widzi-my, że mapa wyświetlana jest w trybie sate-lity. Możemy także włączyć tryb ulic, a także przybliżyć i oddalić mapę dzięki przyciskom pojawiającym się na dole ekranu emulatora (Rysunek 3).

Automatyczne wyświetlenie określonej lokalizacjiKolejną funkcjonalnością, którą dodamy do naszej aplikacji, jest automatyczne przejście do określonej lokalizacji. Domyślnie Google Maps wyświetla mapę Stanów Zjednoczo-nych. Załóżmy, że w naszym przypadku bę-dzie to centrum Warszawy, któremu odpo-wiadają współrzędne 52.227625, 21.004682. Załóżmy także, że chcemy, aby po przejściu do

tej lokalizacji mapa ustawiła się automatycznie na odpowiednim powiększeniu, umożliwiają-cym dostrzeżenie nazw ulic.

W tym celu musimy zadeklarować obiekt typu MapController, który będzie nam słu-żył do wykonywania operacji na mapie. Aby uzyskać taki obiekt, możemy wykonać na zde-finiowanym wcześniej obiekcie mapView me-todę getController(). Następnie definiuje-my dwuelementową tablicę tekstową, do któ-

Listing 3. Automatyczne wyświetlenie określonej lokalizacji mc = mapView.getController();

String coordinates[] = {"52.227625", "21.004682"};

double lat = Double.parseDouble(coordinates[0]);

double lng = Double.parseDouble(coordinates[1]);

p = new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6));

mc.animateTo(p);

mc.setZoom(14);

mapView.invalidate();

Listing 4. Definicja klasy MapOverlay

class MapOverlay extends com.google.android.maps.Overlay

{

public boolean onTouchEvent(MotionEvent event, MapView mapView)

{

if (event.getAction() == 1) {

GeoPoint p = mapView.getProjection().fromPixels(

(int) event.getX(),

(int) event.getY());

Geocoder geoCoder = new Geocoder(getApplicationContext(),

Locale.getDefault());

try {

List<Address> addresses = geoCoder.getFromLocation(

p.getLatitudeE6() / 1E6, p.getLongitudeE6() / 1E6, 1);

String add = "";

if (addresses.size() > 0)

{

for (int i=0; i<addresses.get(0).getMaxAddressLineIndex();

i++)

add += addresses.get(0).getAddressLine(i) + "\n";

add += p.getLatitudeE6() / 1E6 + "; " + p.getLongitudeE6()

/ 1E6;

}

else

add = "Sorry. No information";

Toast.makeText(getBaseContext(), add, Toast.LENGTH_

SHORT).show();

}

catch (IOException e) {

e.printStackTrace();

}

return true;

}

else

return false;

}

}

Page 122: SDJ Extra 34 Biblia

122

Programowanie Android

SDJ Extra 34 Biblia

rej wpisujemy interesujące nas współrzędne. Są one w kolejnym kroku konwertowane do ty-pu double. Operacja przejścia do określonej lo-kalizacji polega tak naprawdę na tym, aby wska-zać mapie punkt, który ma być wyświetlony. Taki punkt to obiekt typu GeoPoint, do które-go przekazujemy nasze współrzędne. Kolejnym krokiem jest wywołanie metody animateTo() z parametrem GeoPoint, która przeniesie nas do określonej lokalizacji. Na koniec wywołu-jemy metodę setZoom z wartością 14 – bo ta-kie powiększenie nas interesuje. Na sam ko-

niec przeładowujemy mapę przy pomocy meto-dy invalidate() obiektu mapView. Treść tych operacji przedstawiłem na Listingu 3.

Wyświetlanie adresu klikniętej lokalizacjiOstatnią funkcjonalnością, którą chcemy do-dać do naszej aplikacji, jest wyświetlanie komu-nikatu z adresem klikniętej lokalizacji. W tym celu napiszemy własną klasę MapOverlay, któ-ra będzie rozszerzała klasę com.google.android.maps.Overlay. Wewnątrz tej klasy dekla-rujemy metodę onTouchEvent(MotionEvent

event, MapView mapView). Pierwszą czyn-nością wewnątrz tej metody jest sprawdzenie typu operacji, który miał miejsce. Musimy wy-konać nasze zadanie w momencie, kiedy użyt-kownik dotknął ekranu telefonu. Takie spraw-dzenie zapewni nam warunek następujący kod event.getAction() == 1. Następnie musi-my zdefiniować obiekt znanego już nam typu GeoPoint, do którego przypisujemy współrzęd-ne klikniętego/dotkniętego miejsca. Całą opera-cję pobrania/wyszukania informacji o punkcie zapewni nam obiekt typu Geocode. Odpowia-da on za proces zwany Geocoding'iem – czy-li ustalaniem współrzędnych na podstawie na-zwy lokalizacji, np. ulicy, placu. Umożliwia on także wykonywanie operacji odwrotnej – czyli ustalenie nazwy na podstawie współrzędnych. Po pobraniu tych informacji budujemy z nich odpowiedni komunikat, który wyświetlany jest na mapie przy pomocy obiektu Toast, służące-go do wyświetlania krótkich komunikatów dla użytkowników (Rysunek 4). Jeśli żadna infor-macja nie zostanie odnaleziona, wówczas wy-świetlany jest komunikat Sorry. No information.

Aby umieścić tak przygotowaną warstwę na mapie, należy odwołać się do niej w głów-nej funkcji onCreate(). W tym celu dekla-rujemy obiekt naszej klasy MapOverlay. Na-stępnie na obiekcie mapView wołamy metodę GetOverlays(), która zwraca nam wszystkie warstwy, które następnie usuwamy (metoda clear()). Na koniec dodajemy nasz obiekt typu MapOverlay:

MapOverlay mapOverlay = new MapOverlay();

List<Overlay> listOfOverlays =

mapView.getOverlays();

listOfOverlays.clear();

listOfOverlays.add(mapOverlay);

Definicja klasy MapOverlay została przed-stawiona na Listingu 4. Niestety, opisana po-wyżej funkcjonalność nie działa prawidłowo przy polskich ustawieniach regionalnych – wynika to z innego separatora dziesiętnego w sczytywanych współrzędnych (wymagana jest kropka, a w polskich ustawieniach jest to przecinek). Musimy niestety zmienić na-sze ustawienia regionalne na amerykańskie.

Na koniec ważna informacja o pakietach, które muszą zostać zaimportowane w celu poprawnego skompilowania naszej aplika-cji. Lista pakietów, która powinna zostać za-importowana, przedstawiona została na Li-stingu 5.

PodsumowanieAndroid to bijąca obecnie wszelkie rekordy popularności platforma służąca do pisania za-awansowanych aplikacji na telefony komór-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.

Listing 5. Lista importowanych pakietów

import java.io.IOException;

import java.util.List;

import java.util.Locale;

import com.google.android.maps.GeoP

oint;

import com.google.android.maps.MapAct

ivity;

import com.google.android.maps.MapCon

troller;

import com.google.android.maps.MapV

iew;

import com.google.android.maps.Over

lay;

import android.location.Address;

import android.location.Geocoder;

import android.os.Bundle;

import android.view.MotionEvent;

import android.widget.Toast;

import android.view.KeyEvent;

Rysunek 4. Informacje o adresie zaznaczonej lokalizacji

IGOR KRUKIgor 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: [email protected], http://www.igorkruk.pl

Page 123: SDJ Extra 34 Biblia
Page 124: SDJ Extra 34 Biblia

124

Programowanie Android

SDJ Extra 34 Biblia

Programowanie UI

www.sdjournal.org 125

Google Android jest systemem operacyjnym na urządzenia mo-bilne i jednocześnie platformą

do tworzenia oprogramowania (Andro-id SDK). Pierwsza wersja systemu zosta-ła wydana przez firmę Google już prawie 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. Również znaczna część core'owych elemen-

tów systemu została stworzona przy uży-ciu tej technologii.

Dobry start pozwala sądzić, że system utrzyma się na rynku systemów opera-cyjnych i platform programistycznych na urządzenia mobilne mimo konkurencji i doświadczenia takich marek jak Windows ME czy Symbian OS. Fakt ten może być dobrym przyczynkiem do zainteresowa-nia się platformą i poczynienia pierwszych kroków w zakresie tworzenia na nią pro-stych aplikacji.

Android technicznieGoogle Android od strony technicznej jest platformą, w której wyróżnić można czte-ry warstwy. Rysunek 1 jest ich ilustracją.

Najniżej położona jest warstwa core'owa (Linux Kernel), oparta na jądrze Linuksa (ver. 2.6), odpowiedzialna za niskopozio-mowe usługi systemowe takie jak: bezpie-czeństwo, zarządzanie pamięcią i procesa-mi, obsługa sterowników.

Wyżej jest warstwa bibliotek i środowi-ska uruchomieniowego (Libraries i Andro-id Runtime). Zawiera ona zestaw biblio-tek C/C++ używanych przez różne kompo-nenty systemu i wystawionych do użytku przez developerów poprzez warstwę wyż-szą (Application Framework). Warstwa druga zawiera również środowisko uru-chomieniowe, czyli napisaną przez Go-ogle'a maszynę wirtualną (DVM – Dalvik

Google Android

Artykuł jest wprowadzeniem do programowania interfejsu użytkownika (UI – User Interface) na platformie Google Android. Omawia podstawowe komponenty interfejsu i sposoby rozmieszczania ich na ekranie. Przedstawia również tworzenie formularzy, przenoszenie danych między formularzami oraz interakcję między różnymi ekranami użytkownika.

Dowiesz się:• Jak poprawnie rozmieścić komponenty

komponenty użytkownika na ekranie;• Jak tworzyć poszczególne widgety;• Jak ekrany użytkownika współpracują ze sobą.

Powinieneś wiedzieć:• Podstawowa znajomość języka Java• Znajomość budowy dokumentów XML• Znajomość Eclipse IDE

Poziom trudności

Programowanie interfejsu użytkownika pod Android OS

Rysunek 1. Architektura systemu operacyjnego Google Android (źródło: http://developer.android.com)

Page 125: SDJ Extra 34 Biblia

124

Programowanie Android

SDJ Extra 34 Biblia

Programowanie UI

www.sdjournal.org 125

Virtual Machine) dostosowaną do pracy na urządzeniach mobilnych.

Warstwa kolejna (Application Fra-mework) zawiera framework do tworze-nia aplikacji pod Androida czyli stanowi wspomniane wcześniej API programistycz-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żytkownikaW 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-cja użytkownika ze wszystkimi warstwami systemu. Celem projektowania jest więc uczynienie tej komunikacji jak najbardziej prostą i efektywną. Często mówiąc o pożą-danych cechach interfejsu stosuje się okre-ślenie „przyjazny w stosowaniu dla użyt-kownika” (ang. user friendly). Oznacza to osiągnięcie jakiegoś efektu w aplikacji w możliwie najprostszy i najszybszy sposób bez angażowania dużej ilości komponen-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.

Programowanie na urządzeniach mobil-nych ma swoje dodatkowe wymagania. Do-chodzi w tym przypadku mały ekran oraz okrojone możliwości interakcji osoby pra-cującej z aplikacją mobilną przejawiają-cą się np. w braku kursora myszki. Choć z drugiej strony, tą niedogodność nadra-biają w ostatnim czasie ekrany dotykowe, znacznie poszerzające wachlarz sposobów komunikowania się z systemem.

Komponenty interfejsu użytkownikaSystem Google Android, jak każdy system operacyjny, zawiera zestaw komponentów do tworzenia graficznego interfejsu użyt-kownika. Komponenty te, zwane widgeta-mi, pozwalają konstruować zaawansowa-ne ekrany umożliwiające w wygodny spo-sób realizację wyszukanych funkcji aplika-cji. Na pewno ich funkcjonalność nie od-biega od kontrolek systemowych dostarcza-

nych ze stacjonarnymi systemami operacyj-nymi. Ponadto Android OS daje możliwość programiście rozszerzania komponentów czy też tworzenia zupełnie nowych, bazu-jąc w tym zakresie na klasycznych mechani-zmach dziedziczenia dostępnych w Javie.

Hierarchia klas Wszystkie widgety Android OS dziedzi-czą po abstrakcyjnej klasie View i znajdują się w pakiecie android.widget. Hierarchia klas komponentów użytych w przykłado-wej aplikacji, na Rysunku 2.

Rysunek 2. Hierarchia klas komponentów użytych w przykładzie.

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

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

�����������

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

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

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

�������

Listing 1. Tworzenie pola tekstowego w kodzie Javy

public class HelloAndroid extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

TextView tv = new TextView(this);

tv.setText("Hello, Android");

setContentView(tv);

}

}

Listing 2. Tworzenie pola tekstowego w xml

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

<TextView xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:text="Hello, Android"/>

Listing 3. Plik AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="pl.example.biorithm.activity"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".InputDataForm" android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

<uses-sdk android:minSdkVersion="3" />

</manifest>

Page 126: SDJ Extra 34 Biblia

126

Programowanie Android

SDJ Extra 34 Biblia

Programowanie UI

www.sdjournal.org 127

WidgetyObiekty 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.

LayoutyObiekty 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-gramista może tworzyć wprost w kodzie, korzystając z określonych klas i interfej-sów reprezentujących widgety i ich zacho-wania. Jest to sposób przypominający po-dejście do budowania znane ze wspomnia-nej już biblioteki Swing. Przykład jak mo-że wyglądać kod pola tekstowego jest na Li-stingu 1).

Dla wielu programistów taka droga jest pewnie bardziej naturalna i prosta. Gorzej sytuacja wygląda, gdy zaistnieje koniecz-ność całościowego spojrzenia na kod inter-fejsu użytkownika. Również utrzymanie i rozwój takiego kodu jest trudny.

Deklaratywnie Drugim sposobem tworzenia interfej-sów pod Android OS jest deklaratywne umieszczanie komponentów w dokumen-tach XML. W tym przypadku elementy mają postać tagów, które odpowiadają po-szczególnym klasom widgetów. Ten spo-sób z kolei, przypomina budowanie inter-fejsu znane z aplikacji internetowych. Źró-dło tworzonego interfejsu ma postać za-gnieżdżonych między sobą tagów doku-mentu XML. Sposób ten przypomina więc

Przykładowe komponenty

Lista komponentów dostarczonych wraz z systemem jest dość długa. Poniższe punkty przedstawiają najważniejsze z nich:

• LinearLayout – podstawowy layout do rozmieszczania elementów horyzontalnie lub wertykalnie

• RelativeLayout – pozwala na relatywne rozmieszczanie komponentów, zalecany przy skomplikowanych interfejsach użytkownika

• TableLayout – umożliwia umieszczanie widgetów w formie tabeli• DatePicker – pozwala na wygodny wybór daty (kalendarzyk)• TimePicker – komponent umożliwiający wybór godziny• Button, Checkbox, TextView, EditView – standardowe elementy służące do budowy

formularzy• Spinner – odpowiednik listy rozwijanej• AutoCompleteTextView – komponent do pobierania tekstu z automatyczną podpo-

wiedzią wyboru• ListView – widget pozwalający na tworzenie przewijanej, pionowej listy z możliwo-

ścią filtrowania elementów• Gallery – komponent najczęściej używany do tworzenia galerii zdjęć – umożliwia

tworzenie poziomej, przewijanej listy elementów, wybrany element jest umieszcza-ny na środku listy

• TabWidget – widget implementujący funkcjonalność zakładek (tabs)• MapView – umożliwia tworzenie ekranów użytkownika z włączoną usługą Google-

Maps• WebView – pozwala na programowanie ekranów użytkownika z możliwością prze-

glądania zasobów internetu

Rysunek 3. Konfigurowanie projektu Android

Page 127: SDJ Extra 34 Biblia

126

Programowanie Android

SDJ Extra 34 Biblia

Programowanie UI

www.sdjournal.org 127

tworzenie stron www w technologii html. Na Listingu 2 pokazaliśmy jak w pliku xml zdefiniować pole tekstowe podobne do te-go z Listingu 1.

Należy jednak zaznaczyć, że nie wszyst-ko da się deklaratywnie umieścić w pli-kach xml. Część funkcjonalności danego komponentu niekiedy trzeba oprogramo-wać w kodzie klasy. Z racji jednak rozdzie-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-droid OS.

Struktura aplikacji pod Android OSZanim przejdziemy do tworzenia inter-fejsu użytkownika na bazie przykładowej aplikacji, przedstawimy w kilku zdaniach jak wygląda podstawowa struktura aplika-cji działającej pod Androidem. Wiemy już, że jednym z budulców aplikacji są kompo-nenty dziedziczące po klasie View. Repre-zentują one elementy interfejsu użytkow-nika. Jednakże podstawowym budulcem aplikacji pod Android OS są klasy dzie-dziczące po klasie Activity. Zgodnie z do-kumentacją klasy Activity stanowią poje-dyńczą jednostkę aplikacji, która jest za-projektowana do wykonywania akcji użyt-kownika. Aplikacja może składać się z wie-lu klas Activity lecz użytkownik zawsze wchodzi w interakcję tylko z jedną z nich. Klasy Activity mają metodę public void onCreate(Bundle savedInstanceState) wywoływaną przez Android OS w czasie startu danej akcji. Tu właśnie należy inicjo-wać komponenty interfejsu użytkownika.

Przykładowa aplikacjaNajlepiej jest zrozumieć tworzenie i dzia-łanie komponentów interfejsu użytkow-nika wykonując praktyczne przykłady. W tym miejscu opiszemy budowanie prostej aplikacji działającej pod Android OS. Bę-dzie to popularna aplikacja pokazująca wy-kresy biorytmów na konkretny dzień lub przedział dni.

Po podaniu odpowiednich danych (daty urodzenia użytkownika, przedziału czaso-wego i rodzaju wykresu biorytmu) aplika-cja pokaże na nowym ekranie wygenerowa-ny biorytm. Wezmą więc udział w tym pro-cesie dwa ekrany użytkownika i jedna akcja.

Do tworzenia projektu będziemy potrze-bować odpowiedniego środowiska. Będzie nim: poprawnie zainstalowany Android SDK w dowolnej wersji (sugerowana wer-sja: 1.5), środowisko programistyczne Ec-lipse, plugin do Eclipse'a ADT (Andro-id Development Toolkit) z wbudowanym emulatorem telefonu komórkowego.

Listing 4. Klasa InputDataForm zaraz po wygenerowaniu

public class InputDataForm extends Activity {

/** Metoda wywoływana, gdy obiekt jest tworzony */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

}

Listing 5. Plik input_data_form.xml z definicjami elementów formularza

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical" ...>

<LinearLayout android:orientation="vertical" ...>

<TextView android:text="@string/birthdayText" .../>

<TableLayout ...>

<TableRow>

<TextView android:text="@string/day" .../>

<TextView android:text="@string/month">

<TextView android:text="@string/year"/>

</TableRow>

<TableRow>

<EditText android:id="@+id/birthDay"/>

<Spinner android:id="@+id/birthMonth" .../>

<AutoCompleteTextView android:id="@+id/birthYear" .../>

</TableRow>

</TableLayout>

</LinearLayout>

<LinearLayout android:orientation="vertical" ...>

<TextView android:text="@string/biorithmText" .../>

<TableLayout ...>

<TableRow>

<TextView android:text="@string/dateFromText" .../>

<TextView android:id="@+id/dateFromContent" .../>

<TextView android:text="@string/dateToText" .../>

<TextView android:id="@+id/dateToContent" .../>

</TableRow>

<TableRow>

<Button android:id="@+id/pickDateFrom" android:text="Zmień" .../>

<Button android:id="@+id/pickDateTo" android:text="Zmień" .../>

</TableRow>

</TableLayout>

</LinearLayout>

<LinearLayout android:orientation="vertical" ...>

<TextView android:text="@string/graphText" .../>

<LinearLayout android:orientation="vertical" ...>

<CheckBox android:id="@+id/physical" android:text="Fizyczny" .../>

<CheckBox android:id="@+id/mental" android:text="Psychiczny" .../>

<CheckBox android:id="@+id/emotional" android:text="Intelektualny" .../>

</LinearLayout>

</LinearLayout>

<LinearLayout android:orientation="horizontal" ...>

<Button android:id="@+id/generate" android:text="@string/generateText" .../>

</LinearLayout>

</LinearLayout>

Page 128: SDJ Extra 34 Biblia

128

Programowanie Android

SDJ Extra 34 Biblia

Programowanie UI

www.sdjournal.org 129

W dalszej części opisu zakładamy, że wy-mienione składniki zostały poprawnie za-instalowane.

Tworzenie projektuPierwszym krokiem jest utworzenie w Ec-lipse IDE projektu Android. Z menu File / New / Project / Android / Android Project wybieramy opcję projektu (zakładamy, że wtyczka jest poprawnie zainstalowana). Uzupełniamy wymagane pola, po wykona-niu tych czynności powinniśmy mieć okno konfiguracji podobne do tego z rysunku 3:

W polu Create Activity zdefiniowalismy pierwszą klasę typu Activity o nazwie InputDataForm. Klasa ta będzie zawierać prosty formularz do pobrania danych. W wygenerowanej strukturze projektu może-my od razu zajrzeć do pliku AndroidMani-fest.xml. Znajdują się tam podstawowe da-ne konfiguracyjne dla całej aplikacji. Tu właśnie należy definiować klasy Activity (Listing 3).

Z Listingu 3 widzimy, że do definicji klasy InputDataForm podłączony został filtr decydujący o uruchomianiu właśnie tej Activity zaraz po starcie aplikacji.

Sama klasa InputDataForm wygląda tak, jak na Listingu 4.

Jak widać, na Listingu 4 w meto-dzie public void onCreate(Bundle

savedInstanceState) ustawiana jest kla-sa View zawierająca komponenty: setContentView(R.layout.main). Przedstawio-na forma oznacza, że widok będzie gene-rowany z pliku xml. Aby się o tym prze-konać, spójrzmy na plik main.xml znajdu-jący się w katalogu res/layout. Przy okazji możemy zmienić jego nazwę na input_da-ta_form.xml. Tutaj właśnie, korzystając z tagów odpowiadających poszczególnym klasom komponentów zdefiniujemy naj-pierw: odpowiedni layout dla formularza, później umieścimy na nim komponenty.

Definiowanie layoutówJak już wspomnieliśmy, formularz będzie posiadał elementy: pola do podania daty urodzenia, zakresu czasu na jaki ma być wygenerowany biorytm oraz do wyboru rodzaju wykresu. Wydaje się, że najodpo-wiedniejszym rozwiązaniem będzie w tym przypadku wybór LinearLayout umiesz-czającego komponenty w porządku piono-wym. To będzie zewnętrzny, główny layout. Każde z tych elementów będzie zdefinio-wane dodatkowo w layoutcie poziomym, a konkretnie w TableLayout. Pamiętajmy, że np. element formularza do wprowadzenia daty urodzenia, będzie się składał z 3 pól (dzień, miesiąc, rok urodzenia) i to właśnie te pola umieścimy w TableLayout.

Podobnie drugi element – pola do wy-boru zakresu dat. Pola te zrealizujemy za

Listing 6. Komponent Spinner w input_data_form.xml

<Spinner android:id="@+id/birthMonth"

android:layout_width="wrap_content"

android:layout_height="wrap_content" android:drawSelectorOnTop="true"

android:width="20pt"

android:prompt="@string/months_prompt" />

Listing 7. Kod obsługi klasy Spinner w klasie InputDataForm.

birthMonth = (Spinner) findViewById(R.id.birthMonth);

ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.birthMonth,

android.R.layout.simple_spinner_item);

adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

birthMonth.setAdapter(adapter);

Listing 8. Plik day_values.xml z miesiącami wyświetlanymi w komponencie Spinner

<resources>

<string-array name="birthMonth">

<item>styczeń</item>

<item>luty</item>

<item>marzec</item>

<item>kwiecień</item>

<item>maj</item>

<item>czerwiec</item>

<item>lipiec</item>

<item>sierpień</item>

<item>wrzesień</item>

<item>październik</item>

<item>listopad</item>

<item>grudzień</item>

</string-array>

</resources>

Listing 9. Definicja komponentów Button, pod które podpięty został komponent DatePickerDialog

<Button android:id="@+id/pickDateFrom"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_column="1"

android:text="Zmień"/>

<Button android:id="@+id/pickDateTo"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_column="3"

android:text="Zmień"/>

Listing 10. Obiekty nasłuchujące podpięte pod buttony.

pickDateFrom.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

showDialog(DATE_FROM_DIALOG_ID);

}

});

pickDateTo.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

showDialog(DATE_TO_DIALOG_ID);

}

});

Page 129: SDJ Extra 34 Biblia

128

Programowanie Android

SDJ Extra 34 Biblia

Programowanie UI

www.sdjournal.org 129

pomocą komponentu DatePickerDialog. Aby pola te i opisy tekstowe do nich były również efektywnie rozmieszczone, zasto-sujemy także TableLayout.

Trzeci element, czyli pola wyboru rodza-ju wykresu, zrealizujemy za pomocą kom-ponentów Checkbox. Jednak te elemen-ty umieścimy w kolejnym LinearLayout, również zorientowanym na pionowe (ang. vertical) rozmieszczanie zawartości. Spójrzmy jak może wyglądać plik input_data_form.xml po zmianach wprowadzo-nych zgodnie z powyższymi uwagami (Li-sting 5). Listing ten ma za zadanie głównie pokazać rozmieszczenie layoutów stąd też niepotrzebne atrybuty zostały usunięte.

Uważny czytelnik Listingu 5 z pewnością zauważył, że tak naprawdę TableLayouty za-gnieździliśmy jeszcze w kolejnych elemen-tach LinearLayout. Było to konieczne ze względu na elementy opisowe poszczegól-nych sekcji formularza (czyli swego rodzaju labele), które zaimplementowane są za po-mocą komponentu TextView. Dokładnie są to napisy: Data urodzenia, Biorytm, Wykres.

Oto jak może wyglądać ekran użytkow-nika zdefiniowany w omawianym pliku in-put_data_form.xml (Rysunek 4):

Pole do wprowadzania dnia urodzenia jest typowym komponentem służącym do po-bierania tekstu. Realizuje je klasa EditView. Ciekawsze rozwiązanie przyjęliśmy przy po-lu do wprowadzania miesiąca urodzenia. Użyty jest tam komponent Spinner będą-cy klasyczną listą rozwijaną. Oto fragment pliku input_form_data.xml definiujący ten komponent (Listing 6).

Jak już wspomnieliśmy, nie wszystko da się zdefiniować w samych plikach xml. Kompo-nent Spinner, aby działał poprawnie musi mieć jeszcze w klasie InputDataForm, w metodzie onCreate(Bundle savedInstanceState) pod-łączony adapter. Wygląda to tak (Listing 7).

Całość listy rozwijanej na Rysunku 5.Przy okazji możemy zdradzić jak zdefi-

niowaliśmy miesiące znajdujące się na li-ście rozwijanej. Rozwiązaniem jest umiesz-czenie danych, które chce się wyświetlić na liście, w pliku xml znajdującym się w kata-logu res aplikacji (w tym katalogu, znajdują się wszystkie statyczne składniki aplikacji). Dokładnie chodzi tu o katalog res/values za-wierający statyczne i stałe łańcuchy zna-ków. W katalogu tym zdefiniowaliśmy plik day_values.xml (Listing 8).

Pole do wpisania roku urodzenia za-implementowaliśmy korzystając z kom-ponentu AutoCompleteTextView. Jest to również dość popularny widget (zwłasz-cza w aplikacjach webowych) wyświetla-jący użytkownikowi podpowiedzi dosto-sowane do tego, co już użytkownik wpi-sał w pole. Nadmienimy tylko, że kompo-nent ten również wymaga podłączenia ada-ptera (w klasie InputDataForm w metodzie onCreate(Bundle savedInstanceState), oraz że zbiór podpowiedzi (czyli w tym przypadku lata od 1900 do 2000) zde-finiowany jest w tablicy years w klasie InputDataForm.

Do zaimplementowania sekcji do wy-znaczania zakresu dat biorytmu użyliśmy komponentów kalendarzyka, czyli w An-droid OS – DatePickerDialog. Pod zwy-

kły komponent Button została podpięta akcja pokazująca stosowne okno dialogo-we. Fragment w input_form_data.xml wy-gląda tak (Listing 9).

Na Listingu 9 widzimy dwa buttony, je-den odpowiedzialny za przyjęcie daty od której wykres biorytmu ma być generowa-ny, drugi – służący do przyjęcia daty koń-cowej.

W kodzie Javy obsługa DatePickerDialog wygląda następująco (Listing 10). W metodzie onCreate(Bundle savedInstanceState) pod buttony podpięte są nasłuchiwacze zda-rzeń (ang. listenery).

Metoda showDialog(int id) z kla-sy Activity jest sprzężona z metodą onCreateDialog(int id), w której two-rzone są komponenty kalendarza, w zależ-ności od wyboru – pobierającego datę Od lub datę Do (Listing 11).

Ostatnią sekcją formularza są kompo-nenty Checkbox, określające jakiego rodza-ju wykres biorytmu ma być wygenerowany i wyświetlony na ekranie użytkownika. W input_data_form.xml mają one postać na-stępującą (Listing 12).

Komunikacja między ekranami użytkownikaDane wprowadzone przez użytkowni-ka na powyżej opisanym interfejsie mu-simy w końcu przetransportować do kla-sy Graph (drugi ekran użytkownika) od-powiedzialnej za generowanie wykresu. W Android OS stosuje się w takich przypad-kach obiekty klasy Intent, która jest, mó-wiąc w skrócie, zwykła mapą do przeno-

Rysunek 4. Ekran z formularzem do wprowadzania danych.

Rysunek 5. Komponent Spinner (lista rozwijana) w działaniu

Rysunek 6. Końcowy efekt – ekran pokazujący wygenerowany wykres

Page 130: SDJ Extra 34 Biblia

130

Programowanie Android

SDJ Extra 34 Biblia

szenia danych między klasami Activites. Przykładowo, tworzenie obiektu Intent ma postać konstruktora: Intent intent

= new Intent(this, Graph.class). Od razu można zauważyć klasę Activity, do której przeniesione zostanie sterowa-nie. Ustawienie wartości do przeniesie-nia czyli parametru wygląda tak: intent.putExtra(Constants.DATE_OF_BIRTH,

dateOfBirth). W ten sposób w metodzie onCreate(Bundle savedInstanceState) w klasie Graph możemy już odebrać prze-kazane parametry (w podanym przykła-

dzie jest to data urodzenia) i na ich podsta-wie wygenerować wykres.

Samo uruchomienie przejścia pomiędzy dwoma ekranami użytkownika wykonuje metoda klasy Activity : startActivityForResult(intent, SHOW_GRAPH_OK).

Wygenerowany biorytmZa pokazanie na interfejsie użytkowni-ka wygenerowanych biorytmów odpowia-da klasa Graph. To w niej następuje odwo-łanie do obiektu BiorithmCalculator wy-konującego całą logikę obliczania bioryt-

mów. Klasa ta również korzysta z obiektu GraphView (nasz własny komponent dzie-dziczący po klasie View) do budowy inter-fejsu użytkownika. Konkretnie rzecz uj-mując, klasa GraphView „rysuje” wykresy. Definicja ekranu użytkownika odpowie-dzialnego za pokazanie wykresów znajdu-je się tam, gdzie znajdują się pliki xml ze zdefiniowanymi layoutami dla poszczegól-nych ekranów użytkownika, czyli w katalo-gu res/layout, w pliku graph.xml. Plik ten wygląda, tak jak na Listingu 13.

Układ ekranu zgodny z plikiem graph.xml oraz oczywiście sam wykres, zgodny z danymi wejściowymi pokazany-mi na Rysunku 3, przedstawia Rysunek 6.

PodsumowanieTworzenie interfejsu użytkownika pod An-droid OS jest proste, choć z początku może wydawać się nieprzyjemne, z racji operowa-nia tagami w xml przy definiowaniu kom-ponentów. Dla zatwardziałych zwolenni-ków programowalnego tworzenia kompo-nentów zawsze pozostaje możliwość pisa-nia kodu w klasach Activity w metodach onCreate(Bundle savedInstanceState). Zapewniamy jednak, że przestawienie się na myślenie o interfejsie użytkownika w kategoriach tagów xml'a jest dość szybkie. Później już dość trudno znów zacząć pisać kod obsługujący komponenty w klasach. Po prostu myśli się już trochę tak jak przy projektowaniu dokumentów html.

Android OS charakteryzuje się również bogatym zestawem komponentów pozwa-lającym na realizację nawet najbardziej skomplikowanych interfejsów. W przypad-ku developerów niezbyt zadowolonych ze standardowej biblioteki, zawsze pozosta-je możliwość rozszerzania istniejących już komponentów.

Z racji tego, że Android tworzony jest przez Google'a, istnieje łatwy dostęp do takich komponentów jak MapView czy WebView, umożliwiających korzystanie w tworzonych aplikacjach z Google Maps oraz pozwalających na łatwe przeglądanie zawartości Internetu. To znacznie posze-rza krąg pomysłów na aplikacje, już na star-cie pozwala uczynić je bardziej ciekawymi i konkurencyjnymi.

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-droid OS umocni swoją pozycję w świecie platform programistycznych na urządze-nia mobilne.

TOMASZ MILCZAREKKonsultant w firmie BNS IT.Kontakt z autorem: [email protected]

Listing 11. Metoda onCreateDialog(int id)

protected Dialog onCreateDialog(int id) {

switch (id) {

case DATE_FROM_DIALOG_ID:

return new DatePickerDialog(this, dateFromSetListener, yearFrom,

monthFrom, dayFrom);

case DATE_TO_DIALOG_ID:

return new DatePickerDialog(this, dateToSetListener, yearTo, monthTo,

dayTo);

}

return null;

}

Listing 12. Komponenty checkbox w pliku input_data_form.xml

<CheckBox android:id="@+id/physical"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Fizyczny" />

Listing 13. Plik graph.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:paddingBottom="50px"

android:gravity="right">

<pl.com.jcode.biorithm.view.GraphView

android:id="@+id/graphView"

android:layout_width="fill_parent"

android:layout_height="wrap_content" />

<Button

android:id="@+id/back"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="@string/backText" />

</LinearLayout>

W Sieci

• Android SDK do ściagnięcia – http://developer.android.com/sdk/1.5_r2/index.html • Instalacja Android SDK i wtyczki ADT do Eclipse'a – http://developer.android.com/sdk/1.5_

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/

app/package-summary.html

Page 131: SDJ Extra 34 Biblia
Page 132: SDJ Extra 34 Biblia

132

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 133

Tak jak inne produkty Apple, tak i iPhone zachwyca użytkowników swoim wyglądem, estetyką i jako-

ścią wykonania. Pierwszy moment styczno-ści, pierwsze uruchomienia wbudowanych aplikacji i już widać, co jest tu najważniej-sze: prosta i intuicyjna obsługa. Jeden kla-wisz oraz wielodotykowy ekran (ang. multi-touch screen) to wszystko, czego potrzebuje-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

się zderzyć z pewnymi niedogodnościami. Trzeba tu wymienić dwie najistotniejsze sprawy: mało popularny język programo-wania Objective-C, którego używanie jest wymagane przy tworzeniu aplikacji korzy-stających z natywnego interfejsu użytkow-nika, oraz wymóg posiadania systemu ope-racyjnego Mac OS, który jest niezbędny do instalacji iPhone SDK.

Architektura iPhone OSArchitektura systemu operacyjnego iPho-ne jest bardzo podobna do architektu-ry systemu Mac OS X. Obydwa te rozwią-zania oparte są na podobnym jądrze i ma-

ją strukturę warstwową, przedstawioną na Rysunku 1. Warstwy Core OS oraz Core Se-rvices zawierają fundamentalny interfejs systemu operacyjnego. Wykorzystuje się je w celu uzyskania dostępu do plików oraz gniazd sieciowych (ang. network sockets). W tej warstwie definiowane są również ni-skopoziomowe typy danych. Poszczególne struktury wchodzące w skład warstwy Co-re Services to:

• Address Book: jest to interfejs umożliwia-jący przeglądanie oraz edycję poszcze-gólnych rekordów bazy danych kontak-tów.

• Core Foundation: odpowiada między in-nymi za: zarządzanie danymi oraz ich kolekcjami, operacje na ciągach znaków, przetwarzanie preferencji użytkownika oraz zarządzanie wątkami.

• Core Location: umożliwia odczyt współ-rzędnych geograficznych urządzenia po-branych na podstawie danych z modułu

Poznajemy iPhone SDK

Nikt nie zaprzeczy, że jednym z najpopularniejszych urządzeń mobilnych ostatnich 12 miesięcy jest iPhone. Doskonały wygląd zewnętrzny, przejrzysty graficzny interfejs użytkownika oraz bogata funkcjonalność to tylko niektóre jego cechy. W niniejszym artykule przedstawię to urządzenie z punktu widzenia programisty.

Dowiesz się:• Podstawowych informacji na temat iPhone OS;• Co zawiera iPhone SDK oraz jak się z nim

programuje.

Powinieneś wiedzieć:• Jak programować w języku Objective-C.

Poziom trudności

Pierwsze kroki

Rysunek 1. Architektura iPhone OS

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

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

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

������

����������

�����������

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

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

�������

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

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

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

Page 133: SDJ Extra 34 Biblia

132

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 133

GPS, operatora sieci komórkowej lub po-łączenia WIFI.

• CFNetwork: jest strukturą odpowiada-jącą za obsługę połączeń sieciowych. Interfejs ten pozwala na tworzenie po-łączeń z wykorzystaniem gniazd BSD, tworzenie szyfrowanych połączeń zgodnych z SSL oraz TLS. Wspiera-ne protokoły to m.in. HTTP, HTTPS oraz FTP.

• SQLite: odpowiada za dostęp do wbudo-wanej bazy danych typu SQL.

• XML Support: umożliwia parsowanie dokumentów XML.

Kolejna warstwa – Media, wykorzysty-wana jest przy dostępie do grafiki 2D, 3D, audio oraz video. Warstwa ta składa się z takich technologii jak: OpenGL ES, Quartz, oraz Audio Core. Media to rów-nież Animation Core, czyli zaawansowa-ny silnik animacji oparty na języku Ob-jective-C.

Najbardziej istotną warstwą w systemie iPhone OS jest warstwa Cococa Touch. Za-wiera ona między innymi takie infrastruk-tury jak:

• UIKit Framework: jest jedną z pod-stawowych bibliotek wykorzystywa-nych przy tworzeniu aplikacji w iPho-ne SDK. Pełni ona rolę obsługi interfej-su graficznego, zdarzeń, zarządza apli-kacją, jej oknami oraz interakcją z użyt-kownikiem.

• Foundation Framework: jest swoistego rodzaju opakowaniem (ang. wrapper) dla danych dostępnych w warstwie Core Services. Interfejs ten odpowia-da za kolekcje danych, operacje na cią-gach znaków oraz między innymi za zarządzanie datą i czasem, preferen-cjami użytkownika czy wątkami i pę-tlami.

• Address Book UI Framework: umożli-wia integrację zewnętrznych aplika-cji z natywną bazą danych kontaktów. Aplikacje mogą uruchamiać poszcze-gólne widoki niezbędne przy dodawa-niu kontaktów, ich edycji oraz prze-glądaniu.

iPhone SDKNajbardziej aktualną wersję pakietu iPho-ne SDK można ściągnąć ze strony http://developer.apple.com/iphone. Wersja, z któ-rej korzystałem podczas pisania niniejsze-go artykułu, to 2.2.1. Aby pobrać SDK, niezbędne jest posiadanie swojego Apple ID. Jest to identyfikator, który reprezen-tuje poszczególną osobę korzystającą z pro-duktów Apple. W przypadku, gdy jeszcze nie posiadasz tego identyfikatora, zapra-szam do wypełnienia formularza rejestra-

cyjnego (http://developer.apple.com/iphone/program/start/register/). Po zalogowaniu się na wyżej wymienionej stronie pobieramy SDK oraz przeprowadzamy proces instala-cji. Pik z SDK w wersji 2.2.1 zajmuje oko-ło 1.7 GB (jest to obraz dysku). Podwój-ne kliknięcie rozpoczyna proces podłącze-nia dysku oraz uruchamia menadżer pli-ków. Podwójnym kliknięciem uruchamia-

my plik iPhone SDK, który jest programem instalacyjnym.

Do kolejnego kroku przechodzimy, na-ciskając przycisk Continue. Kolejny ekran daje możliwość zapoznania się z licencją SDK, którą akceptujemy, naciskając Conti-nue. Dalej pojawia się ekran wyboru doce-lowej partycji, na której SDK będzie zain-stalowane. Aby zainstalować iPhone SDK

Listing 1. Implementacja metody applicationDidFinishLaunching

- (void)applicationDidFinishLaunching:(UIApplication *)application

{

[ window addSubview:[ navigationController view ]];

[ window makeKeyAndVisible ];

}

Listing 2. Implementacja metody viewDidLoad

- (void)viewDidLoad

{

[ super viewDidLoad ];

self.title = @"First View";

}

Listing 3. Implementacja metody numberOfSectionsInTableView

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return 1;

}

Listing 4. Implementacja metody numberOfRowsInSection

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection

(NSInteger)section

{

return 6;

}Listing 5. Implementacja metody cellForRowAtIndexPath

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:

(NSIndexPath *)indexPath

{

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [ tableView dequeueReusableCellWithIdentifier:

CellIdentifier ];

if (cell == nil)

{

cell = [[[ UITableViewCell alloc ] initWithFrame:CGRectZero reuseIdentifier:

CellIdentifier ] autorelease ];

}

NSString* label = [ NSString stringWithFormat:@"cell %d", indexPath.row ];

[ cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator ];

[ cell setText:label ];

return cell;

}

Listing 6. Definicja funkcji didSelectRowAtIndexPath

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath

*)indexPath

{

}

Page 134: SDJ Extra 34 Biblia

134

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 135

w wersji 2.2.1, wymagane jest około 5GB wolnej przestrzeni dyskowej. Aby konty-nuować proces instalacji, zaznaczamy od-powiednią partycję, a następnie naciska-my przycisk Continue. Ostatnim etapem konfiguracji instalatora jest wybór narzę-dzi, które mają zostać zainstalowane. Wy-braną konfigurację należy zaakceptować przyciskiem Continue. Rozpoczyna się pro-ces instalacji.

Pokrótce postaram się omówić zawartość zainstalowanej paczki. W skład SDK wcho-dzą narzędzia niezbędne do produkcji apli-

kacji, ich testów oraz instalacji na urządze-niu z platformą iPhone OS. Są to XCode, Interface Builder oraz Instrumenty. XCo-de to IDE (ang. Integrated Development Envi-ronment) wykorzystywane przy tworzeniu aplikacji zarówno dla systemu Mac OS X, jak i dla iPhone OS. Narzędzie to umożli-wia proste i wygodne zarządzanie projek-tami, składanie paczek wykonywalnych (zarówno w wersjach pod symulator oraz urządzenie), debugowanie aplikacji oraz ich instalację na urządzeniu końcowym. Stworzenie pierwszej aplikacji przy pomo-

cy XCode jest niezwykle proste. Do dys-pozycji mamy wygodny generator szablo-nów aplikacji oraz klas. Pomocny okazu-je się również tryb debugowania w edyto-rze kodu, który umożliwia między innymi podgląd wartości poszczególnych zmien-nych bez przełączania się w tryb pełnego debuggera. XCode radzi sobie bez proble-mu z dopełnianiem, kolorowaniem skład-ni, ukrywaniem bloków kodu oraz - na co warto zwrócić uwagę - pozwala definiować oraz stosować własne makra. Wspomniane 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-ność połączeń sieciowych czy historię do-

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

Rysunek 2. Wybór szablonu aplikacji

Page 135: SDJ Extra 34 Biblia

134

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 135

stępów do systemu plików. Wyniki działa-nia narzędzi przedstawione są w przejrzy-stej formie graficznej. Ciekawostką jest to, iż wspomniane testy można przeprowadzić zarówno na symulatorze, jak i na urządze-niu końcowym.

Pierwszy programPrzy tworzeniu pierwszego projektu sko-rzystam z przygotowanych w XCodzie sza-blonów aplikacji. Po uruchomieniu IDE z menu File wybieram opcję New Project. Na-stępnie w oknie przedstawionym na Ry-sunku 2 wybieram szablon Navigation-Ba-sed Application, po czym wprowadzam na-zwę projektu. W ten sposób XCode wyge-nerował pierwszy projekt definiujący pro-stą aplikację. Przed przystąpieniem do edy-cji wygenerowanych klas warto sprawdzić aktualny wygląd aplikacji. Po naciśnięciu kombinacji klawiszy command+R program kompiluje się oraz uruchamia w symulato-rze.Zawiera ona jedynie pustą tabelę oraz pusty pasek nawigacji.

Pora rozpocząć implementację. Pra-wa strona IDE prezentuje drzewo projek-tu. W katalogu Classes znajdują się aktu-alnie tylko dwie klasy: AppDelegate oraz RootViewController. Pierwsza klasa jest delegatem aplikacji – główną klasą projek-tu, zaś druga klasa definiuje widok tabeli. Listing 1 przedstawia metodę, która wy-woływana jest w momencie, gdy aplikacja zostaje uruchomiona. Zadaniem tej meto-dy jest przygotowanie interfejsu graficzne-go użytkownika. Poszczególne widoki zo-stają tu dodane do okna aplikacji; w przy-padku omawianego programu jest to wi-dok nawigacji.

Rzućmy teraz okiem na zawartość kla-sy RootViewController. Klasa ta defi-

niuje pustą tabelę, która była widoczna wcześniej w oknie symulatora. Uzupeł-niając kolejne metody, zmienimy tytuł ta-beli oraz wypełnimy jej komórki przykła-dowymi tekstami. Metoda przedstawiona na Listingu 2 zostanie wywołana w mo-mencie, gdy widok zostanie załadowany

na ekran. Tytuł tego widoku zmienimy na First View.

Listing 3 przedstawia metodę, która wy-woływana jest przez system po to, by spraw-dzić, ile jest sekcji w tabeli. Tabela może zo-stać podzielona na wiele sekcji, które są póź-niej grupowane. W naszym przypadku po-

Rysunek 3. Widok pierwszej tabeli aplikacji

Listing 7. Nagłówek klasy CustomCell

@interface CustomCell : UITableViewCell

{

UILabel* upperText;

UILabel* lowerText;

}

-(void) setUpper:( NSString* )text;

-(void) setLower:( NSString* )text;

@end

Listing 8. Inicjalizacja obiektu DetailsTableViewCotroller

- (id) initWithFrame:(CGRect)frame

reuseIdentifier:( NSString *) reuseIdentifier

{

if ( self = [super initWithFrame:frame

reuseIdentifier:reuseIdentifier ])

{

// Initialization

UIView *view = self.contentView;

lowerText= [[ UILabel alloc ] init ];

UIFont* font = [ UIFont boldSystemFontOfSize:20.0 ];

lowerText.font = font;

[ view addSubview:lowerText ];

[ lowerText release ];

upperText = [[ UILabel alloc ] init ];

font = [ UIFont systemFontOfSize:20.0 ];

upperText.font = font;

[ view addSubview:upperText ];

[ upperText release ];

}

return self;

}

Listing 9. Implementacja metody layoutSubviews

- (void)layoutSubviews {

[super layoutSubviews];

// getting the cell size

CGRect contentRect = self.contentView.bounds;

CGFloat boundsX = contentRect.origin.x;

CGRect frame;

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

upperText.frame = frame;

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

lowerText.frame = frame;

}

}

Page 136: SDJ Extra 34 Biblia

136

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 137

zostawimy tu domyślną wartość 1. Następ-nie (patrz: Listing 4) system sprawdza, ile rzędów w tabeli jest dostępnych dla danej

sekcji. Jeśli tabela ma wiele sekcji, to wspo-mniana metoda jest wywoływana dla każdej sekcji oddzielnie. Numer sekcji przekazywa-

ny jest jako parametr section. W naszym przypadku zwracamy przykładową wartość 6: chcemy, aby tabela miała wypełnionych sześć komórek.

Dla każdej z komórek wywoływana jest metoda przedstawiona na Listingu 5. W tej metodzie tworzymy i inicjalizujemy poszczególne wiersze tabeli. Numer aktu-alnie tworzonej komórki przekazany jest w parametrze indexPath. W pierwszej li-nii tego listingu definiujemy identyfika-tor danego obiektu. Mechanizm ten umoż-liwia powtórne korzystanie z wcześniej skonstruowanego obiektu. Ponowne uży-cie obiektu jest o wiele szybsze niż two-rzenie go na nowo. Po tych kilku krokach komórka tabeli jest gotowa do wypełnie-nia danymi.

Dalej tworzymy ciąg znaków przecho-wujący słowo komórka (ang. cell), do któ-rego dopiszę numer wiersza danej komór-ki. Za pomocą następnej linii [ cell

setAccessoryType: UITableViewCellAcc

essoryDisclosureIndicator ]; określa-my typ elementu graficznego, który zosta-nie wyświetlony po prawej stronie każde-go wiersza.

Typ tej grafiki powinien odpowiadać ak-cji, jaka może zostać wykonana dla danej komórki. W przedstawionym przypadku element ten ma być zachętą do kliknięcia. Kolejna linia listingu przedstawia umiesz-czenie przygotowanego wcześniej ciągu znaków w utworzonej komórce. Po uru-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ę DetailsTableViewCotroller. 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 nad drugim. Pozwoli to na przedstawienie Rysunek 4. Okno wyboru podklasy nowo tworzonego obiektu

Listing 10. Metody służące do ustawiania zawartości etykiet komórki

-(void) setLower:(NSString*)text

{

lowerText.text = text;

}

-(void) setUpper:(NSString*)text

{

upperText.text = text;

}

Listing 11. Implementacja metody viewDidLoad w klasie DetailsTableViewCotroller

- (void)viewDidLoad

{

[ super viewDidLoad ];

self.title = @"second view";

moreButton = [[ UIBarButtonItem alloc ] initWithTitle:@"More"

style:UIBarButtonItemStylePlain

target:self

action:@selector(onMore)];

self.navigationItem.rightBarButtonItem = moreButton;

}

Listing 12. Metody wykorzystywane przy konstrukcji tabeli w klasie DetailsTableViewCotroller

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return 1;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

return 2;

}

Page 137: SDJ Extra 34 Biblia

136

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 137

przykładowych atrybutów oraz wartości im przypisanych. Do tego celu niezbędne bę-dzie stworzenie kolejnej klasy, która będzie dziedziczyć po klasie UITableViewCell. Po-wtarzamy w tym celu kroki opisane w przy-padku tworzenia DetailsTableViewCotr

oller. Zamiast UITableViewController z ekranu przedstawionego na Rysunku 4 wy-bieramy UITableViewCell jako bazę two-rzonej klasy.

Nowa klasa nazywać się będzie CustomCell. Listing 7 przedstawia jej na-główek.

Aby wyświetlić dwie linie tekstu w da-nej komórce, skorzystamy z dwóch kompo-nentów typu UILabel. Kolejne linie wier-sza nazwiemy upperText oraz lowerText. Listing 7 przedstawia również dwie meto-dy, które wykorzystamy do ustawienia war-tości poszczególnych etykiet. Pora zajrzeć do implementacji klasy. Do wygenerowanej przez XCode metody widocznej na Listin-gu 8 należy dodać tworzenie obiektów ty-pu UILabel. Etykieta ( ang. label ) to kom-ponent, który wyświetla tekst. W pierwszej linii listingu uruchamiamy metodę klasy bazowej.

W kolejnych liniach listingu tworzę pierwszą etykietę. Następnie przypisuję jej pogrubioną czcionkę systemową o wiel-kości 20 pikseli. Komponent ten następ-nie dodajemy do widoku danej klasy. W ten sam sposób zostaje stworzony kolejny wiersz komórki.

Aby poprawnie umiejscowić komponen-ty na ekranie, dodamy metodę przedsta-wioną na Listingu 9. Najpierw wywołuje-my metodę z klasy bazowej, po czym pobie-ramy rozmiar aktualnego widoku. W kolej-nych liniach tworzymy ramy o poszczegól-nych parametrach, które następnie zostają przypisane do utworzonych wcześniej kom-ponentów.

Listing 10 przedstawia wspomniane wcześniej metody setLower i setUpper, które będą wykorzystane do ustawienia wartości poszczególnych etykiet komórki. Przekazane w parametrach teksty zosta-ją zapisane w poszczególnych etykietach wiersza.

Powróćmy teraz do implementacji DetailsTableViewCotroller. Listing 11 przed-stawia metodę, która wywoływana jest po załadowaniu widoku. Oprócz ustawienia tytułu widoku tworzymy przycisk, któ-ry widoczny będzie w pasku nawigacji. Kolejne parametry używane przy inicja-lizacji tego obiektu określają: tekst, któ-ry będzie zawierał dany przycisk oraz styl przycisku, klasę, która ma zostać powia-damiana o użyciu danego przycisku, oraz metodę (ang. selector), która będzie wy-woływana po jego przyciśnięciu. Zmien-ną moreButton dodaję do nagłówka klasy

Listing 13. Metoda przedstawiająca tworzenie komórek typu CustomCell

- (UITableViewCell *)tableView:(UITableView *)tableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

static NSString *CellIdentifier = @"DetailsCell";

CustomCell *cell =

(CustomCell*)[ tableView dequeueReusableCellWithIdentifier:CellIdentifier ];

if (cell == nil)

{

cell = [[[ CustomCell alloc] initWithFrame:CGRectZero

reuseIdentifier:CellIdentifier ]

autorelease ];

}

switch (indexPath.row) {

case 0:

[cell setUpper:@"First row upper text"];

[cell setLower:@"First row lower text"];

break;

case 1:

[cell setUpper:@"Second row upper text"];

[cell setLower:@"Second row lower text"];

break;

default:

break;

}

return cell;

}

Listing 14. Nagłówek klasy DetailsTableViewController

@interface DetailsTableViewCotroller :

UITableViewController <UITableViewDelegate>

{

UIBarButtonItem* moreButton;

}

Listing 15. Metoda, która określa wysokość pojedynczej komórki w tabeli

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath

*)indexPath

{

return 60.0;

}

Listing 16. Metoda wyświetlająca przykładowy dialog

-(void) onMore

{

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"MessageTitleText"

message:@"More information"

delegate:nil

cancelButtonTitle:@"Hide me"

otherButtonTitles:nil];

[alert show];

[alert release];

}

Page 138: SDJ Extra 34 Biblia

138

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 139

DetailsTableViewCotroller w postaci: UIBarButtonItem* moreButton;

Listing 12 przedstawia omówione wcze-śniej metody numberOfSectionsInTabl

eView oraz numberOfRowsInSection. Po-zostało jedynie stworzenie poszczegól-

Listing 17. Tworzenie obiektu DetailsTableViewController

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

DetailsTableViewCotroller* secondTable =

[[ DetailsTableViewCotroller alloc ]

initWithStyle:UITableViewStyleGrouped ];

sdjAppDelegate* appDelegate =

[ UIApplication sharedApplication ].delegate;

[[ appDelegate navigationController]

pushViewController:secondTable

animated:YES];

[secondTable release];

}

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

- (void)sendHTTPMessage:(NSData*)body toURL:(NSString*)url

{

NSURL* url2 = [NSURL URLWithString:url];

NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url2];

[request setHTTPMethod:@"POST"];

[request setHTTPBody:body];

[request setValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];

NSHTTPURLResponse* response = [[[NSHTTPURLResponse alloc] init] autorelease];

}

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

NSHTTPURLResponse* response = [[[NSHTTPURLResponse alloc] init] autorelease]; NSData* resp = [NSURLConnection sendSynchronousRequest:

requestreturningResponse:&response error:err];

Listing 20. Implementacja metody didReceiveResponse

- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

{

[self.receivedData setLength:0];

}

Listing 21. Implementacja metody didReceiveData

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

{

[self.receivedData appendData:data];

}

Listing 22. Obsługa nieudanego połączenia

- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

{

[self.delegate connectionDidFail:error];

[connection release];

}

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

- (void) connectionDidFinishLoading:(NSURLConnection *)connection

{

//(...)

}

Page 139: SDJ Extra 34 Biblia

138

Programowanie iPhone OS

SDJ Extra 34 Biblia

iPhone SDK

www.sdjournal.org 139

nych elementów tabeli. Listing 13 przed-stawia sposób, w jaki konstruujemy kolej-ne komórki. Tym razem tworzone wier-sze są zdefiniowanego wcześniej typu CustomCell. Następnie sprawdzamy in-deks aktualnej komórki, by wypełnić ją odpowiednim tekstem, po czym zwracam stworzony obiekt.

Aby wszystko prezentowało się poprawnie na ekranie, konieczne jest ustawienie wyso-kości pojedynczej komórki tabeli. Modyfiku-ję nagłówek klasy DetailsTableViewController, dodając protokół UITableViewDelegate ( Listing 14 ), oraz implementuję metodę heightForRowAtIndexPath przedstawioną na Listingu 15.

Implementację tabeli detali kończymy, de-finiując metodę onMore. Tworzymy w niej dialog, który będzie wyświetlał przykładowy tekst. Inicjalizacja tego dialogu polega na po-daniu jego tytułu, treści oraz tekstu, jaki ma się pojawić na przycisku zamykającym dany dialog. Jak to zrobić w praktyce, przedstawia Listing 16.

Pozostaje jeszcze tworzenie obiek-tu nowej tabeli, które znajdzie się w me-todzie didSelectRowAtIndexPath, w kla-sie RootViewController. Jest ona wy-wołana po kliknięciu komórki tabeli RootViewController. Listing 17 pokazuje, jak tworzony jest obiekt typu DetailsTableViewController oraz w jaki sposób mo-żemy dodać go do widoku nawigacji. War-to zauważyć, że przy inicjalizacji obiektu tabeli podajemy jej typ. W tym przypadku jest to typ UITableViewStyleGrouped. Styl ten charakteryzuje się grupowym wyświe-tlaniem poszczególnych sekcji. Nowo utwo-rzony obiekt dodajemy do widoku nawiga-cji, co powoduje animowane wyświetlenie

tabeli detali. Na pasku nawigacyjnym auto-matycznie pojawia się przycisk z tytułem wi-doku. Przycisk ten umożliwia powrót do po-przedniej tabeli.

Aby mieć możliwość instalacji aplikacji na urządzeniu, należy dołączyć do iPho-ne Developer Program. Uczestnictwo w tym programie jest niestety płatne. Pod-stawowe członkostwo kosztuje 99$. Wraz z jego wykupieniem otrzymujemy certy-fikat, który jest niezbędny w celu podpi-sania aplikacji przed jej instalacją. Certy-fikat ten jest również niezbędny przy wy-syłaniu własnych aplikacji do sklepu Ap-ple. Osoby zainteresowane instalacją apli-kacji bez podpisywania odsyłam do wyszu-kiwarki Google ;)

Porównanie: iPhone OS vs. Symbian OSJako że w ciągu ostatnich lat przyszło mi pracować zarówno nad aplikacjami pod iPhone, jak i pod Symbian OS, dlatego po-kusiłem się o porównanie tych dwóch plat-form z programistycznego punktu widze-nia. W tym celu postanowiłem przygoto-wać dwie aplikacje: jedną pod Symbia-na, drugą zaś pod iPhone OS, realizujące identyczne zadanie: przesyłanie danych metodą POST poprzez protokół HTTP. W tym miejscu nie będę przytaczał peł-nych źródeł tych aplikacji, skupię się je-dynie na fragmentach dotyczących re-alizacji wspomnianego zadania. Listing 18 obrazuje inicjację połączenia w języ-ku Objective-C. Kilka pierwszych wywo-łań metod to nic innego jak przygotowa-nie adresu URL, ustawienie odpowied-nich wartości nagłówka oraz rozpoczę-cie transakcji poprzez inicjalizację obiek-

tu NSURLConnection. W ten sposób rozpo-czyna się proces asynchronicznego przesy-łania danych. Aby skorzystać z trybu syn-chronicznego, wystarczy zamienić ostat-nią linię Listingu 18 oraz dodać dwie linie przedstawione na Listingu 19. W momen-cie, gdy połączenie zostanie nawiązane, system prześle odpowiedź z serwera, wy-wołując metodę z Listingu 20. Następnie, gdy serwer prześle porcję danych, urucho-miona zostanie metoda z Listingu 21.

Zakończoną sukcesem transakcję po-twierdza metoda connectionDidFinish

Loading ( Listing 23 ), zaś wszelkie błę-dy połączenia powinny zostać obsłużone w metodzie przedstawionej na Listingu 22. Niestety, realizacja podobnego zada-nia na platformie Symbian jest o wiele bar-dziej skomplikowana. Aby przeanalizować ten proces, zapraszam do przejrzenia przy-kładu znajdującego się na stronie http://wiki.forum.nokia.com/index.php/How_

to_Make_an_HTTP_Connection_Using_

TCP/IP_with_RSocket. Wygląda na skom-plikowane, prawda? I niestety – takie jest w rzeczywistości...

Do tego dochodzą nieco pokraczne idio-my narzucane przez Symbiana (np. stos czyszczenia czy deskryptory). Realizacja te-go zadania pod iPhone jest o niebo prostsza i bardziej intuicyjna (zakładając oczywiście dobrą znajomość języka Objective-C). Pod-sumowując to szybkie porównanie: w mo-jej subiektywnej ocenie – zwycięża zdecy-dowanie iPhone OS!

PodsumowanieW powyższym artykule przedstawiłem wstęp do programowania aplikacji pod 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 DUBIKPracuje 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-my jak iPhone OS, Symbian OS/S60 oraz Palm OS.Kontakt z autorem: [email protected]

Rysunek 5. Tabela typu DetailsTableViewController

Rysunek 6. Wygląd prostej aplikacji wygenerowanej przez XCode

Page 140: SDJ Extra 34 Biblia

140

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 141

Jeśli Twoja praca zawodowa wiąże się z programowaniem urządzeń mobil-nych, to zapewne masz Drogi Czytel-

niku doświadczenia bądź to z językiem Ja-va, tudzież z C++. Prymat tych języków w mobilnym sektorze rynku wydawał się nie-zachwiany od kilku dobrych lat. A tu nagle niespodzianka!

Nagle na horyzoncie pojawiał się nieco ezoteryczny język Objective-C, napędzany potężną marketingowo-biznesową machiną stojącą za nową platformą Apple: iPhone/iTouch. Prawdę mówiąc, gdyby jakieś dwa lata temu ktoś oznajmił mi, że niedługo przyjdzie mi bliżej obcować z Objective-C, uśmiechnąłbym się zapewne lekcewa-żąco. Dziś o tym języku słychać sporo; wy-nika to z prostego faktu: chcąc programo-wać natywne aplikacje pod iPhone/iTouch, nie mamy praktycznie żadnej alternatywy. I tak oto język, używany dotąd przez wą-ską grupę programistów natywnych apli-kacji dla MacOS, bazujących na Cocoa, stał się nagle niespodziewanie ważny, zaś na wzmiankę o konieczności jego nauki ra-czej nikt się już nie uśmiecha. Tak więc dro-gi Programisto Java/C++, jeżeli stwierdziłeś

(tudzież, Twój szef stwierdził za Ciebie), iż nadszedł czas na rozpoczęcie nauki języka Objective-C, to zapraszam do lektury ni-niejszego artykułu. Postaram się przedsta-wić ten temat z perspektywy znajomych Tobie języków i przekonać Ciebie, że – jak mówi stare przysłowie – nie taki diabeł straszny jak go malują.

W tym miejscu pozwolę sobie jedynie dodać, iż poniższy artykuł nie pretenduje do miana podręcznika Objective-C. Nale-ży go raczej uznać za mocno skondensowa-ny przegląd możliwości wspomnianego ję-zyka, połączony z szeregiem odniesień do C++ i Java, oraz rozszerzony o garść wska-zówek i drogowskazów dla tych, którzy chcieliby na poważnie kontynuować naukę Objective-C.

Po trzykroć: Witaj Świecie!Znane chińskie powiedzenie mówi, iż jeden fragment kodu źródłowego wart jest tysiąca słów (hmm... chyba coś pokręciłem...). Z tego względu zdecydowałem, iż zanim przejdę do omówienia poszczególnych właściwości oma-wianego języka, chciałbym zaproponować Ci ciekawy eksperyment w postaci prezentacji i analizy trzech aplikacji typu Witaj Świecie! zaprogramowanych kolejno w C++, Java oraz Objective-C.

Jak wskazuje Paul Graham w jednym ze swoich esejów (patrz ramka W sieci), anali-za takich małych programów może zaowo-

cować zaskakująco dużą liczbą ciekawych wniosków.

Do dzieła więc! Na Listingach 1, 2 oraz 3 zaprezentowane są źródła programów Wi-taj Świecie! zapisanych w C++, Java i Objecti-ve-C. Na dobry początek sugeruję przejrzenie tych Listingów.

Przy pisaniu niniejszego artykułu zało-żyłem sobie, iż przeznaczony on będzie dla osób znających stosunkowo dobrze języki C++ oraz Java (patrz sekcja Powinieneś wie-dzieć). Wnioskuję zatem, iż programy przed-stawione na Listingach 1 i 2 nie wymagają dogłębnych wyjaśnień. W przypadku Witaj Świecie w C++ wita nas wysłużona funkcja main, stanowiąca dziedzictwo języka C. Dy-rektywa using namespace świadczy o tym, że język zaprojektowany przez Bjarne Stro-ustrupa wspiera przestrzenie nazw, zaś dość nietypowa składnia:

cout << "Hello, World!" << endl;

przypomina o tym, że C++ pozwala przeła-dowywać operatory. Zawartość Listingu 2 po-twierdza smutną prawdę, że aby zmusić pro-gram pisany w Javie (tj. w języku czysto obiekto-wym) do wyświetlenia prostego napisu, trzeba stworzyć osobną klasę i wyposażyć ją w publicz-ną, statyczną metodę main.

Listing 3 przynosi za to szereg niespodzia-nek. Na pierwszy rzut oka wygląda trochę zna-jomo. W pierwszej linii wita nas znajoma dy-rektywa preprocesora #include. Dalej mamy definicję funkcji main. W pierwszej linii tej-że funkcji pojawia się coś jakby wskaźnik do obiektu NSAutoreleasePool, a potem... No właśnie – co potem!? Nagle ni stąd, ni zowąd pojawia się przedziwna składnia:

[[NSAutoreleasePool alloc] init];

Objective-C kontra Java i C++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++.

Dowiesz się:• Jak wyglądają podstawowe konstrukcje ję-

zyka Objective-C;• Jak Objective-C ma się do takich języków jak

Java i C++.

Powinieneś wiedzieć:• Jak programować w języku Java bądź C++.

Poziom trudności

Wprowadzenie do języka

Page 141: SDJ Extra 34 Biblia

140

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 141

Czyżby nowa odmiana Lispa, tyle że z nawi-sami kwadratowymi zamiast okrągłych...? W kolejnej linii wywołanie funkcji NSLog, która – wnioskując po przekazywanym do niej argumencie – zdaje się być odpowiedni-kiem funkcji printf ze standardowej biblio-teki języka C.

Tylko czemu przed literałem napisowym występuje znak @? W kolejnej linii znajduje-my po raz wtóry dziwne kwadratowe nawia-sy, zaś na końcu żegna nas znajoma instruk-cja return 0;.

Takie mniej więcej myśli pojawiały się w mojej głowie, kiedy pierwszy raz czytałem program typu Witaj Świecie! napisany w Ob-jective-C. Pora na wnioski. Patrząc z punk-tu widzenia osoby, która nie zna tego języ-ka, możemy stwierdzić prawie na pewno, że Objective-C:

• jest na pewno bliższy językom C\C++ niż Javie;

• nie jest językiem czysto obiektowym i zapewne, podobnie jak C++, wspiera wiele paradygmatów programowania;

• nie posiada mechanizmu przestrzeni nazw;

• jest wyposażony w preprocesor;• szykuje dla nieobeznanych z nim progra-

mistów semantyczne niespodzianki!

Mam nadzieję, że mój nietypowy ekspery-ment z potrójnym Witaj Świecie! rozbudził Czytelniku Twoją ciekawość. Jeśli chciał-byś dowiedzieć się, jakie jeszcze niespo-dzianki szykuje dla Ciebie język Objecti-ve-C, to zapraszam do dalszej lektury. Na początek...

...krótki rys historycznyHistoria Objective-C zaczyna się we wcze-snych latach 80 dwudziestego wieku. Spró-bujmy wczuć się na chwilę w atmosferę tamtego okresu. W środowisku programi-stów systemowych niepodzielnie króluje ję-zyk C (oczywiście nie opisany jeszcze jako standard ANSI). Obiektowość (dość ezote-ryczny paradygmat programowania stoso-wany przez wąską grupę specjalistów) koja-rzy się przede wszystkim ze... Smalltalk'iem 80 (będącym następnikiem Smalltalk'a 72). Pojawiają się pierwsze próby pożenienia wy-dajności języka C z rewolucyjnymi mecha-nizmami budowania abstrakcji rodem z ję-zyków obiektowych. Jest rok 1983. Bjarne Stroustrup zaczyna pracować nad językiem C++. Z kolei Brad Cox oraz Tom Love, za-łożyciele firmy Stepstone, opracowują język Objective-C.

Aby zrozumieć fundamentalną różni-cę pomiędzy tymi dwoma językami, nale-ży zrozumieć założenia projektowe przy-jęte przez ich autorów. Mówiąc w dużym uproszczeniu, Język C++ postawił na sta-

tyczność oraz pełną kontrolę typów, co w rezultacie miało zaowocować dużą wydaj-nością pisanych w nim programów. Objec-tive-C poszedł za to w zupełnie inną stro-nę: jego projektanci główny nacisk położy-li na charakterystyczny dla Smalltalk'a dy-

namizm, co miało zaowocować większą ela-stycznością języka za cenę (nie)znaczne-go spadku wydajności. Ta fundamentalna różnica w założeniach projektowych spra-wiała, iż pomimo wspólnego korzenia w postaci języka C, C++ i Objective-C w wie-

Listing 1. Program Witaj Świecie! napisany w języku C++

#include <iostream>

using namespace std;

int main()

{

cout << "Hello, World!" << endl;

return 0;

}

Listing 2. Program Witaj Świecie! napisany w języku Java

public class HelloWorld

{

public static void main( String[] args )

{

System.out.println("Hello, World!");

}

}

Listing 3. Program Witaj, Świecie! napisany w języku Objective-C

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSLog (@"Hello, World!");

[pool drain];

return 0;

}

Listing 4. Język C++: plik nagłówkowy klasy Point (Point.hpp)

#ifndef __POINT_HPP_INCLUDED__

#define __POINT_HPP_INCLUDED__

class Point

{

public:

Point( int x = 0, int y = 0 );

int X() const;

int Y() const;

void SetX( int x );

void SetY( int y );

private:

int m_X;

int m_Y;

};

std::ostream& operator<<( std::ostream& os, const Point& p );

#endif // __POINT_HPP_INCLUDED__

Page 142: SDJ Extra 34 Biblia

142

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 143

lu kwestiach różnią się diametralnie. Java, która pojawiła się na rynku ponad 10 lat później niż C++ i Objective-C, miała stano-wić kolejny krok ewolucyjny w stosunku do C++. Ten czysto obiektowy język, również inspirowany pomysłami ze Smalltalk'a, w wielu aspektach zbliża się do Objective-C (widać to chociażby na przykładzie mecha-nizmu refleksji); można by się wręcz poku-sić o tezę, że w zamyśle swoich twórców Ja-va miała stanowić złoty środek pomiędzy C++ a Objective-C.

Od początku lat 80-tych zarówno C++, Java, jak i Objective-C przeszły długą drogę. C++ doczekał się standardu ANSI (C++98), kolejna odsłona standardu tego języka jest właśnie opracowywana (C++0x), zaś sam język nadal cieszy się olbrzymim poważa-niem jako narzędzie programistów syste-mowych, głównie dzięki możliwości stoso-wania potężnych i wysoce wydajnych me-chanizmów abstrakcji w postaci szablonów

(ang. templates). Java – początkowo święcą-ca wielkie tryumfy, powoli traci na znacze-niu jako język wysokiego poziomu, wypie-rana przez bardziej dynamiczne języki (wi-dać złoty środek nie zawsze jest... złoty). Objective-C doczekał się w 2007 roku no-wej odsłony (2.0) i zyskuje na popularności na fali sukcesów firmy Apple. Jak potoczy się dalej ta historia, pokaże czas. Dość hi-storii! W kolejnych podpunktach zajrzymy pod maskę Objective-C i zobaczymy cóż się tam kryje. A jest co oglądać.

Przegląd składniPodobnie jak C++, tak samo Objective-C jest nadzbiorem języka C. Oznacza to, że każdy, poprawnie skonstruowany program, napisa-ny w języku C będzie poprawnie przetwo-rzony przez kompilator Objective-C. W tym ujęciu Objective-C jest zdecydowanie bliż-szy C++, niż Javie, która nadzbiorem języ-ka C nie jest.

Podstawowy element nowości w skład-ni Objective-C wiąże się z mechanizmami programowania obiektowego oraz z tymi, które wiążą się z zestawem nowych słów kluczowych. Objective-C stosuje tutaj dość oryginalną konwencję (np. w porówna-niu do C++): słowa kluczowe specyficzne dla tego języka oznaczone są prefiksem @. Wspomniane słowa kluczowe to: @class, @interface, @implementation, @public, @private, @protected, @try, @catch, @throw, @finally, @end, @protocol, @selector, @synchronized, @defs oraz @encode. Znaczenie większości z wymie-nionych słów poznamy w dalszej części ni-niejszego artylułu.

Objective-C w stosunku do czystego C wprowadza również nowe typy i wartości. Pierwszym z nich jest typ BOOL, służący do reprezentacji wartości boolowskich (odpo-wiednikami tego typu w Javie i C++ są od-powiednio boolean i bool). Co ciekawe, Objective-C stosuje dość rzadko stosowaną konwencję, jeśli chodzi o nazwy stałych bo-olowskich: zmienne typu BOOL przyjmują wartości YES i NO. Zarówno Java, jak i C++ stosują w tym przypadku słowa kluczowe true i false.

Kolejnym elementem nowości są słowa kluczowe nil, Nil oraz id. nil jest odpowied-nikiem wartości NULL w C\C++ i służy do oznaczania pustego wskaźnika na zmienną/obiekt. Nil jest odpowiednikiem nil, słu-żącym do oznaczania pustego wskaźnika na obiekt klasy (w Objective-C klasa jest pełno-prawnym obiektem).

Specyficznym rozwiązaniem stosowanym w Objective-C jest typ SEL. Wartości tego typu mogą przechowywać tzw. selektory, tj. identyfikatory metod. SEL jest w pewnym sensie odpowiednikiem wskaźników na me-tody w języku C++.

Niewątpliwie najbardziej rzucającym się w oczy (niektórzy powiedzieliby: najbar-dziej dziwacznym) elementem języka Ob-jective-C jest składnia wywoływania me-tod obiektów. Jeśli przez całe swoje zawo-dowe życie programowałeś w C++ lub w Ja-vie (tudzież w jednym z wielu innych, po-pularnych języków wspierających paradyg-mat programowania obiektowego, np. C#, Python, Ruby, JavaScript itd.), to najbar-dziej naturalną wydaje Ci się następująca konstrukcja:

obiekt.metoda();

W Objective-C konstrukcja ta wygląda zgo-ła odmiennie:

[obiekt metoda];

Warto w tym miejscu zauważyć, iż taka, nieco egzotyczna, składnia tyczy się je-dynie wywołań metod; wywołania glo-balnych funkcji mają taką samą składnię jak język C czy C++. Co więcej, przed-stawiona wyżej forma składniowa wią-że się z fundamentalną różnicą w znacze-niu stwierdzenia wywołanie metody, któ-ra stanowi jeden z fundamentów modelu obiektowości w języku Objective-C. Mó-wiąc w dużym skrócie, wywołanie meto-dy w tym języku to w rzeczywistości wy-słanie komunikatu do obiektu.

Na koniec tego szybkiego przeglądu skład-ni Objective-C warto zauważyć, iż komenta-rze w tym języku są zaznaczane identycznie jak w Javie i C++: dowolna jest zarówno for-ma blokowa: /* ... */ , jak i komentarze dla pojedynczej linii: // ....

Programowanie obiektowe odkryte na nowoJak mogłeś się przekonać czytając poprzedni podpunkt, po szybkim zapoznaniu się z róż-nicami w składni Objective-C, okazuje się, iż zmian wcale nie ma tak dużo jak by się mogło wydawać. W rzeczywistości fundamentalna

Listing 5. Język C++: plik z implementacją klasy Point (Point.cpp)

#include "Point.hpp"

Point::Point( int x, int y )

: m_X( x ),

m_Y( y )

{

}

int Point::X() const

{

return m_X;

}

int Point::Y() const

{

return m_Y;

}

void Point::SetX( int x )

{

m_X = x;

}

void Point::SetY( int y )

{

m_Y = y;

}

std::ostream& operator<<( std::

ostream& os,

const Point&

p )

{

return os << "x=" << p.X() << ";

y=" << p.Y();

}

Listing 6. Język C++: tworzenie obiektu klasy Point

#include "Point.hpp"

#include <iostream>

using namespace std;

int main()

{

Point p( 5, 10 );

cout << p << endl;

return 0;

}

Page 143: SDJ Extra 34 Biblia

142

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 143

różnica pomiędzy Objective-C a C++ i Javą leży w konstrukcjach programowania obiek-towego. Z tego względu spora część niniejsze-go artykułu skupi się właśnie na opisywaniu tych różnic.

Chcąc scharakteryzować jednym słowem model obiektowości w Objective-C, można by napisać, iż jest on zupełny (ang. strict). Model taki stanowi niewątpliwie dziedzic-two języka Scheme i stoi w silnej opozycji do bardziej ograniczonego (czytaj: statycz-nego) modelu, który został wbudowany w język C++. Język Objective-C pozwala za-rządzać zarówno obiektami, jak i klasami w trakcie wykonania programu (klasy są peł-noprawnymi obiektami). Przy takim mode-lu możliwe jest tworzenie nowych klas, do-dawanie do nich metod czy pobieranie listy składowych – wszystko to w trakcie wyko-nania programu! W tym kontekście język

C++ wraz ze swoim RTTI (ang. Run-Time Type Information) wypada bardzo blado. Ja-va znajduje się mniej więcej pośrodku tej układanki, wbudowany w nią mechanizm refleksji (ang. reflection) możliwościami jest zbliżony do Objective-C, aczkolwiek nieco bardziej restrykcyjny (prawdopodob-nie ze względu na fakt, iż język ten bazuje mocno na C++).

W kolejnych podpunktach rozważymy na przykładach możliwości wszystkich trzech języków w kontekście oferowanych przez nie mechanizmów wsparcia dla programowania obiektowego.

Pokaż mi swoją klasęNaszą podróż poprzez meandry obiekto-wości w Objective-C rozpoczniemy od pro-stego, praktycznego przykładu. Rozważmy implementację prostek klasy reprezentują-

cej punkt w dyskretnej przestrzeni dwu-wymiarowej. Na Listingach 4, 5 oraz 6, przedstawiona jest prosta implementacja takiej klasy, zapisana w języku C++. Na Li-sting 7 pokazana jest implementacja takiej samej klasy w języku Java.

Zakładam, iż Czytelnicy tego artykułu zna-ją bądź to C++, bądź Javę, dlatego nie będę w tym miejscu opisywał szczegółów implemen-tacji klasy Point w tych językach. Będę się za to odnosił do nich przy opisie definicji bliź-niaczej klasy w języku Objective-C. Szkielet takiej implementacji przedstawiony jest na Listingach 8 i 9.

Rozważmy poszczególne fragmenty tych dwóch Listingów. Pierwsza rzecz, która rzuca się w oczy, to zdecydowane rozdzie-lenie interfejsu oraz implementacji klasy. Podział ten jest podkreślony przez dobór nazw słów kluczowych: @interface oraz @implementation. Podobnie jak w przypad-ku C++, definicja (interfejs) klasy oraz jej implementacja mogą być (i zazwyczaj są) umieszczone w odrębnych plikach. Pro-gramując w Objective-C, interfejsy klasy umieszcza się w plikach nagłówkowych (z rozszerzeniem .h; patrz: Listing 8), zaś de-finicje metod w pliku implementacji (z roz-szerzeniem .m; patrz: Listing 9). Podejście to stoi w opozycji do konwencji javowskiej, wedle której cała definicja klasy, tj. zarówno 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-buty i metody nie mogą być pomieszane. Atrybuty definiowane są w sekcji interfej-su, oznaczonej nawiasami klamrowymi (Li-sting 8). Co więcej, o ile atrybuty klasy mo-gą być oznaczone jako publiczne (@public), chronione (@protected) bądź prywatne (@private), o tyle wszystkie metody zade-klarowane w jej interfejsie są domyślnie pu-bliczne. Metody niepubliczne (jeśli takowe są potrzebne) ukryte są w pliku implemen-tacji. Podejście to stanowi istotną różnicę, przede wszystkim w stosunku do C++, w którym publiczne, chronione i prywatne metody mogą być swobodnie pomieszane w definicji klasy. Java, ze swoim mecha-nizmem interfejsów, znajduje się pośrod-ku obydwu rozwiązań. Warto zauważyć, iż Objective-C posiada również słowo klu-czowe @class. Jednakże w przypadku te-go języka słowo to wykorzystywane jest je-dynie do zapisywania poprzedzających de-klaracji klas, co ma służyć do przerywania łańcuchów cyklicznych zależności w pli-kach nagłówkowych (problem bardzo do-brze znany programistom C++). Na koniec warto wspomnieć, iż w przypadku Objecti-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

Listing 7. Język Java: definicja klasy Point

import java.util.*;

public class Point {

private int x;

private int y;

public Point( int x, int y ) {

this.x = x;

this.y = y;

}

public int getX() {

return x;

}

public int getY() {

return y;

}

public void setX( int x ) {

this.x = x;

}

public void setY( int y ) {

this.y = y;

}

@Override public String toString() {

StringBuilder ret = new

StringBuilder();

ret.append( "x=" + x + "; " );

ret.append( "y=" + y );

return ret.toString();

}

public static void main( String args[] ) {

Point p = new Point( 5, 10 );

System.out.println(p);

}

}

Page 144: SDJ Extra 34 Biblia

144

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 145

w językach Java i C++ (przerażonych Czy-telników chciałbym szybko uspokoić: Ob-jective-C pozwala uzyskać efekt składowej statycznej w nieco odmienny sposób: po-przez definicję globalnej zmiennej w pli-ku implementacji oraz dodanie w interfej-sie klasy odpowiednich metod dostępu do tej zmiennej).

Metoda na metodęJako że pomyślnie udało się nam przebrnąć przez ogólny opis syntaktyki deklaracji kla-sy w Objective-C, rozważmy teraz poszcze-gólne zagadnienia zawiązane z tym tema-tem bardziej szczegółowo. W niniejszym podpunkcie przeanalizujemy szczegóły de-finicji metod klasy.

Rzeczą, którą daje się zauważyć na przysło-wiowy pierwszy rzut oka, jest fakt, iż składnia służąca do definicji metod w Objective-C jest mocno odmienna od tej, do której przyzwycza-iły nas języki pokroju C++ bądź Java.

Rozważmy hipotetyczną klasę List, repre-zentującą jednokierunkową listę z dowiąza-niami w języku C++. Załóżmy sobie, iż kla-sa ta oferuje nam następującą, publiczną me-todę:

void List::Insert(void* object, std::

size_t at);

Na podstawie powyższej deklaracji nie-trudno się domyśleć, że obiekty typu List miałyby przechowywać elementy dowol-nego typu (void*), zaś zadaniem rozwa-żanej metody byłaby alokacja nowego wę-zła przechowującego zadany obiekt (argu-ment object) i wstawienie go do listy w określonym miejscu (parametr at). Nagłó-wek podobnej metody w języku Java wy-glądałby tak:

public void insert(Object object, int

at) {

...

Wywołanie takiej metody byłoby podobne zarówno w Javie, jak i w C++ ,i mogłoby wy-glądać na przykład tak:

employees.insert(newEmployee,

newEmployeeIndex);

Przekonajmy się, jak podobną metodę moż-na by zadeklarować w Objective-C. Gdyby-śmy zechcieli przetłumaczyć powyższe de-klaracje w sposób bezpośredni, to w wyniku otrzymalibyśmy, co następuje:

-(void) insert:(id)anObject:(unsigned

int)at

Wywołanie takiej metody (a faktycznie: wysłanie wiadomości insert do obiektu

employees) wyglądałoby w Objective-C na-stępująco:

[employees insert:newEmployee:

newEmployeeIndex]

Poniższa lista przedstawia podstawowe fak-ty związane z programowaniem metod w Objective-C; po jej przestudiowaniu przed-stawionym powyżej przykład stał się jasny:

• nazwa metody w Objective-C posiada pre-fiks: znak minus (–), jeśli jest ona metodą instancji, lub plus (+), jeśli mamy do czy-nienia z metodą klasy (tj. odpowiednikiem metody statycznej w C++ lub w Javie);

• nazwy typów występujących w dekla-racji metody (tyczy się to zarówno ty-pu wartości zwracanej, jak i typów argu-mentów) występują w nawiasach;

• kolejne argumenty metody oddzielone są od siebie znakiem dwukropka (:);

• nazwa metody może być identyczna jak nazwa atrybutu klasy; jest to bardzo przy-datne przy pisaniu akcesorów (ang. getters).

Dziedziczenie, wirtualność i protokołyMoc paradygmatu obiektowego leży w możli-wości tworzenia hierarchii przy pomocy me-chanizmu dziedziczenia. Objective-C wspie-ra oczywiście ten mechanizm, aczkolwiek – w odróżnieniu od języka C++ - nie wspie-ra dziedziczenia wielokrotnego. W zamian za to, podobnie jak język Java, oferuje narzędzia, których zadaniem jest zrekompensowanie te-go braku. Narzędzia te (protokoły oraz kate-gorie klas), oraz sam mechanizm dziedzicze-nia w Objective-C, przedstawię pokrótce w niniejszym podpunkcie.

Podstawowa składnia dziedziczenia w ję-zyku Objective-C przedstawiona jest na Li-stingu 10.

Objective-C, podobnie jak Java, nie oferu-je wielorakich wariantów dziedziczenia (tak jak na przykład C++, który pozwala dzie-dziczyć nieprywatne składniki klasy bazo-wej w trzech trybach: public, private i protected). Dostęp do klasy nadrzędnej od-bywa się za pośrednictwem znajomego pro-gramistom Javy słowa kluczowego super. W Objective-C wszystkie metody są domyśl-nie wirtualne, w związku z tym język ten nie posiada odpowiednika słowa kluczowe-go virtual, stosowanego w C++. To samo tyczy się mechanizmu dziedziczenia wirtu-alnego z C++.

Brak wsparcia dla dziedziczenia wielo-krotnego jest w Objective-C rozwiązany podobnie jak w Javie: przy pomocy inter-fejsów, które w terminologii pierwszego z wymienionych języków zwane są protoko-łami. Rozważmy następujący przykład. Za-łóżmy, iż piszemy aplikację użytkową prze-

znaczoną na urządzenie mobilne. Aplika-cja ta konstruowana jest z widoków. Otóż chcielibyśmy, aby nasz widok reagował na dwa rodzaje zdarzeń: zmiany orientacji urządzenia rejestrowane przez akcelero-metr oraz kontakt stylusa z ekranem do-tykowym. W tym celu, definiowany przez nas widok musi zarejestrować się jako ob-serwator w serwisach oferowanych przez system. Aby jednak to się zadziało, musi on dziedziczyć po określonych interfejsach. Scenariusz taki jest dość powszechnie sto-sowany w przypadku programowania urzą-dzeń mobilnych. Na Listingu 11 przedsta-wiony jest fragment implementacji takiego scenariusza w języku C++.

W tym przypadku AccelerometerObserver oraz StylusObserver jako klasy abstrakcyjne pełnią rolę interfejsów. Warto zauważyć, iż C++ nie udostępnia dedykowanego mecha-nizmu służącego do definiowania interfejsów

Listing 8. Język Objective-C: interfejs klasy Point (plik Point.h)

@interface Point : NSObject

{

@private:

int x;

int y;

}

-(int) getX;

-(int) getY;

-(void) setX:(int)x;

-(void) setY:(int)y;

@end

Listing 9. Język Objective-C: implementacja klasy Point (plik Point.m)

#import "Point.h"

@implementation Point

-(int) getX

{

return x;

}

-(int) getY

{

return y;

}

-(void) setX:(int)x

{

return self->x = x;

}

-(void) setY:(int)y

{

self->y = y

}

@end

Page 145: SDJ Extra 34 Biblia

144

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 145

(tj. abstrakcyjnych klas nie posiadających żad-nych składowych, a jedynie czysto wirtualne metody). Z tego względu programowanie in-terfejsów w C++ to kwestia przyjęcia pewnej konwencji. Inaczej jest w Javie. Język ten ofe-ruje nam dedykowane słowo kluczowe (in-terface) służące do definiowania takich kon-strukcji. Na Listingu 12 pokazane jest, jak opisany wyżej scenariusz mógłby być zaim-plementowany w języku Java.

To, co na samym początku rzuca się w oczy, to większe skondensowanie kodu w stosunku do C++. Zastosowanie słowa klu-czowego interface wiąże się ze specyficz-nymi konsekwencjami (np. metody inter-fejsu z założenia są publiczne), co pozwala uniknąć nadmiarowych słów kluczowych, aczkolwiek wiąże się z częściową utratą ela-styczności. Listing 13 pokazuje z kolei, jak z zagadnieniem obsługi interfejsów radzi so-bie Objective-C.

Analizując przedstawiony fragment ko-du, można odnieść wrażenie, iż jest on znacznie bliższy koncepcji interfejsów w Javie niż abstrakcyjnych klas bazowych w C++.

Jednakże mechanizm protokołów w Ob-jective-C idzie znacznie dalej; już sama na-zwa wskazuje na fundamentalną różnicę: cały czas trzeba pamiętać bowiem o tym, iż wywołanie metody w Objective-C jest w rzeczywistości wysłaniem komunikatu do obiektu. Co ciekawe, Objective-C ofe-ruje specjalną metodę o następującym pro-totypie:

-(BOOL) conformsToProtocol:

(Protocol*)protocol

która pozwala w czasie wykonania progra-mu sprawdzić, czy dany obiekt jest zgodny z zadanym protokołem.

Kilka słów o tworzeniu obiektówMieć definicję klasy to jedno. Aby jednak zmusić nasz program do wykonania kon-kretnego zadania, musimy zaludnić go obiek-tami. W przypadku języka C++ tworzenie obiektów to dość skomplikowany temat. C++ 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 znacznie bardziej zbliżony do Javy, w której 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.

Konstrukcja obiektów w Objective-C jest o 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++:

Employee* employee = new Employee;

można by zapisać w Objective-C tak:

Employee* employee = [[Employee alloc] init]

Warto zauważyć, iż wiadomość alloc jest wysyłana do obiektu reprezentującego kla-

sę, zaś init – do obiektu będącego instancją tej klasy. Na Listingu 14 przedstawiony jest przykład klasy Point rozszerzony o metodę inicjalizującą. Pisząc taką metodę, należy pa-miętać o tym, iż:

• jej nazwa musi się zaczynać od przed-rostka init (jest to dość interesująca kon-

Listing 10. Język Objective-C: podstawowa składnia dziedziczenia

@interface Derived : Base

{

}

@end

Listing 11. Język C++: zastosowanie interfejsów

class AccelerometerObserver

{

public:

virtual void OnAccelerometerEvent(

int dx, int dy, int dz ) = 0;

};

class TouchScreenObserver

{

public:

virtual void OnStylusEvent(

int x, int y ) = 0;

};

class MyAppView : public AccelerometerObserver,

public StylusObserver

{

public:

MyAppView();

// ...

virtual void OnAccelerometerEvent(

int dx, int dy, int dz );

virtual void OnStylusEvent(

int x, int y );

// ...

};

MyAppView::MyAppView()

{

// ...

accelerometerManager.register(this);

touchScreenManager.register(this);

// ...

}

void MyAppView::OnAccelerometerEvent(

int dx, int dy, int dz )

{

// ...

}

void MyAppView::OnStylusEvent(

int x, int y )

{

// ...

}

Page 146: SDJ Extra 34 Biblia

146

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 147

wencja w stosunku do Javy i C++, które narzucają konkretny schemat nazewnic-twa konstruktorów);

• powinna ona zwracać nowy obiekt;• powinna również wywołać metodę init

dla swojej nad-klasy.

Podobnie jak C++, Objective-C pozwala de-finiować dla klasy destruktor w postaci me-

tody dealloc. Metoda ta jest wywoływa-na automatycznie w momencie dealokacji obiektu.

Wyjątki a obsługa błędówObsługa wyjątków w języku Objective-C jest znacznie bliższa temu, co oferuje Java niż C++. Dzieje się tak z racji występowa-nia słowa kluczowego finally, które w C++

nie występuje. Na Listingu 15 przedstawio-ny jest fragment kodu Objective-C obsługu-jący wyjątek.

Zarówno programiści języka C++, jak i Ja-vy nie powinni mieć problemu ze zrozumie-niem tego fragmentu kodu. W bloku @try wykonywana jest operacja, która może rzu-cić wyjątek (action), kolejne bloki @catch obsługują różne typy wyjątków – od bardziej szczegółowych (UserException) do bardziej ogólnych (NSException). Podobnie jak Java i C++, język Objective-C oferuje mechanizm ponownego rzucania wyjątku (w tym celu stosuje się w sekcji @catch słowo kluczowe @throw bez argumentów). Jeśli zachodzi ta-ka potrzeba, to po serii sekcji @catch moż-na umieścić sekcję @finally, której zawar-tość będzie wykonana zawsze przy wyjściu z bloku @try.

RTTI: poznaj swoje obiektyNa koniec naszej krótkiej podróży po labi-ryncie możliwości języka Objective-C war-to wspomnieć kilka słów na temat mechani-zmu RTTI (ang. Run-Time Type Information). W przypadku C++, RTTI sprowadza się do następujących elementów:

• klasa type_info oraz operator typeid (dość ograniczone rozwiązanie służące do porównywania typów);

• operator dynamic _ cast, służący do bezpiecznego rzutowania obiektów w dół hierarchii klas.

Java, w odróżnieniu od C++ ,oferuje znacz-nie bogatszy wachlarz możliwości w ramach mechanizmu refleksji, pozwalającego two-rzyć obiekty klasy na podstawie jej nazwy oraz zdobywać informacje o klasach w trak-cie wykonania programu.

Objective-C oferuje szeroki wachlarz me-chanizmów RTTI, na które składają się:

• metoda isMemberOfClass: określa, czy dany obiekt jest instancją określonej kla-sy;

• metoda isKindOfClass: określa, czy da-ny obiekt jest instancją zadanej klasy bądź jednej z podklas tej klasy;

• conformsToProtocol: określa, czy dany obiekt implementuje wszystkie metody zadanego protokołu;

• respondsToSelector, instancesRespondToSelector: określają, czy dany obiekt implementuje zadaną metodę.

Teoria teorią, a praktyka?Mój znajomy programista, który Objective-C używa od kilku lat (głównie do programo-wania natywnych aplikacji pod MacOS), za-pytany o to, co sądzi o wspomnianym języ-ku, odpowiedział krótko: w tym, do czego go używam, sprawdza się świetnie. Po kilkuna-

Listing 12. Język Java: zastosowanie interfejsów

public interface AccelerometerListener {

void onAccelerometerEvent(int dx, int dy, int dz);

}

public interface TouchScreenListener {

void onStylusEvent(int x, int y);

}

public class MyAppView

implements AccelerometerListener, TouchScreenListener {

public MyAppView() {

AccelerometerManager.register(this);

TouchScreenManager.register(this);

}

public void onAccelerometerEvent(

int dx, int dy, int dz) {

// ...

}

public void onStylusEvent(int x, int y) {

// ...

}

}

Listing 13. Język Objective-C: protokoły

@protocol AccelerometerListener

-(void) onAccelerometerEvent:(int)dx:(int)dy:(int)dz

@end

@protocol TouchScreenListener

-(void) onStylusEvent:(int)x:(int)y

@end

@interface MyAppView : NSObject <AccelerometerListener,

TouchScreenListener>

{

// ...

}

@implementation MyAppView

-(void) onAccelerometerEvent:(int)dx:(int)dy:(int)dz

{

//...

}

-(void) onStylusEvent:(int)x:(int)y

{

//...

}

@end

Page 147: SDJ Extra 34 Biblia

146

Programowanie iPhone OS

SDJ Extra 34 Biblia

Wprowadzenie do języka Objective-C

www.sdjournal.org 147

stu minutach rozmowy okazało się, że wspo-mniany programista wykorzystywał Objec-tive-C do programowania interfejsów użyt-kownika pod system spod znaku Jabłusz-ka. Jako główne zalety języka wymienił je-go elastyczność i dynamiczność. Stwierdził

przy tym, iż mając do dyspozycji Objecti-ve-C o wiele łatwiej przetłumaczyć mu kon-cepcje powstałe w głowie na język zrozumia-ły dla maszyny w porównaniu do języków Java czy C++. Co ciekawe, podobne opinie usłyszałem od innego znajomego: progra-

misty, który przez kilka lat tworzył aplika-cje pod Symbian OS, zaś niedawno zaczął pisać programy pod iPhone'a. Jego komen-tarze na temat Objective-C były zadziwia-jąco podobne do przedstawionych powyżej. Wniosek nasuwa się sam: Objective-C, a w zasadzie jego elastyczny model obiektowo-ści, sprawiają, iż język ten cechuje się bardzo dużą siłą wyrazu, szczególnie przy tworze-niu aplikacji użytkowych (np. programowa-nie interfejsów użytkownika czy obsługa ko-munikacji sieciowej). C++ wygrywa niewąt-pliwie w kategorii aplikacji niskopoziomo-wych, narzędzi czy złożonych programów wymagających optymalizacji w celu uzyska-nia ultra-wysokiej wydajności. Wychodzi na to, że najbardziej poszkodowana z tego poje-dynku wychodzi Java: w tym przypadku zło-ty środek wcale nie jest taki złoty. Java nad-rabia jednak zaległości olbrzymią bazą kodu, dostępem do niezliczonej liczby świetnej ja-kości narzędzi oraz świetną maszyną wirtu-alną, którą można oprogramować również w bardziej elastycznych językach. W tej sytu-acji oczywiste jest, iż warto zapoznać się z Objective-C. Chociażby w celu rozszerzenia horyzontów własnej wiedzy.

PodsumowanieW powyższym artykule przedstawiłem naj-ważniejsze konstrukcje języka Objective-C, porównując je przy tym do rozwiązań dostęp-nych w językach Java oraz C++. Jeśli uważnie przestudiowałeś niniejszy tekst, to nie powi-nieneś mieć kłopotów z rozumieniem progra-mów pisanych w tym języku. Ze względu na ograniczenie objętości artykułu wiele istot-nych zagadnień (np. zarządzenia pamięcią) zostało pominiętych. Jeśli jesteś zaintereso-wany pogłębieniem swojej wiedzy z zakresu Objective-C, to zapraszam do lektury ramki zatytułowanej Nauka Objective-C. Ze swojej strony żywię nadzieję, iż zaprezentowany ma-teriał zachęci Cię do zapoznania się z tym cie-kawym językiem, a być może również będzie impulsem do rozpoczęcia eksperymentów z programowaniem aplikacji pod iPhone.

Listing 14. Język Objective-C: definicja klasy Point z metodą inicjalizującą

@interface Point : NSObject

{

int x;

int y;

}

-(id) initWithX:(int)anX andY:(int)anY;

@end

@implementation Point

-(id) initWithX:(int)anX andY:(int)anY

{

if (![super init])

return nil;

x = anX;

y = anY;

return self;

}

@end

// ...

Point* p1 = [[Point alloc] initWithX:5 andY:10];

Listing 15. Język Objective-C: obsługa wyjątku

@try

{

action();

}

@catch (UserException* e)

{

handleUserException();

}

@catch (NSException* e)

{

handleNSException();

@throw

}

@finally

{

cleanup();

}

RAFAŁ KOCISZPracuje na stanowisku Dyrektora Techniczne-go w firmie Gamelion, wchodzącej w skład Gru-py BLStream. Rafał specjalizuje się w technolo-giach związanych z produkcją oprogramowa-nia na platformy mobilne, ze szczególnym na-ciskiem na tworzenie gier. Grupa BLStream po-wstała, by efektywniej wykorzystywać potencjał dwóch szybko rozwijających się producentów oprogramowania – BLStream i Gamelion. Firmy wchodzące w skład grupy specjalizują się w wy-twarzaniu oprogramowania dla klientów korpo-racyjnych, w rozwiązaniach mobilnych oraz pro-dukcji i testowaniu gier. Kontakt z autorem: [email protected]

Nauka Objective-COczywistym jest, iż w krótkim artykule nie da się przekazać pełnej wiedzy na temat tak zło-żonego języka, jak Objective-C. Jeśli zaprezentowany temat wzbudził Twoje zainteresowa-nie, to chciałbym Ci zaproponować konkretne materiały, które pomogą Ci pogłębić wiedzę z 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-wany From C++ to Objective-C. Angielskojęzyczna, elektroniczna i całkowicie darmowa wer-sja tego dokumentu znajduje się pod adresem: http://ktd.club.fr/programmation/fichiers/cpp-objc-en.pdf. Notabene, materiał tam zaprezentowany stał się dla mnie impulsem do napisa-nia niniejszego artykułu. Gdybyś z kolei zechciał uczyć się Objective-C od podstaw w sposób bardziej systematyczny, to polecam książkę Programming in Objective-C 2.0 autorstwa Ste-phen'a G. Kochan'a. Oprócz tego, w Internecie znajdziesz moc dokumentacji oraz tutoriali na temat programowania w tym języku.

Page 148: SDJ Extra 34 Biblia

148

Programowanie JavaME

SDJ Extra 34 Biblia

JME na przykładzie

www.sdjournal.org 149

W chwili obecnej technologia Java jest dzielona przez firmę Sun na trzy wersje: Standard (SE), Enterprise

(EE) i Micro (ME) Edition. Każda z tych wersji jest przeznaczona dla urządzeń o różnych para-metrach sprzętowych. Pierwsza (SE) dystrybu-cja Javy jest przeznaczona dla komputerów oso-bistych, druga (EE) przeznaczona jest dla du-żych, wydajnych serwerów, trzecia zaś (ME), najmniejsza, została zaprojektowana z myślą o urządzeniach o bardzo ograniczonych zaso-bach, takich jak telefony komórkowe lub palm-topy. Ze względu na ograniczenia techniczne ta-kich urządzeń, tj. wolniejsze procesory, mniejszą pamięć, Java ME posiada swój własny, okrojony w stosunku do większych dystrybucji zbiór klas zwanych konfiguracją (ang. configuration). Spe-cyfikacja JME określa obecnie dwie konfigura-cje: CDC (Connected Device Configuration) oraz CLDC (Connected Limited Device Configura-tion). Konfiguracja jest z kolei podstawą do two-rzenia profili (Profile). Profile, bazując na specy-fikacji konfiguracji, rozszerzają jej możliwości. Jednym z profili dostępnych dla konfiguracji CLCD jest MIDP (Mobile Information Device Pro-file), który udostępnia funkcje sieciowe, kompo-nenty interfejsu użytkownika i lokalną pamięć stałą. Pozwala stworzyć prosty interfejs użyt-kownika i podstawowe funkcje sieciowe, korzy-stając z HTTP 1.1. Kombinacja CLDC wraz z profilem MIDP to najczęściej spotykane połą-

czenie. Na jego bazie tworzone są aplikacje zwa-ne MIDletami, wśród których wyróżnić może-my gry lub inne aplikacje wykorzystywane przy-kładowo w biznesie. Gotowy MIDlet może być uruchamiany na wszystkich urządzeniach, któ-re mają zaimplementowaną maszynę wirtualną dla konfiguracji CLDC i profilu MIDP.

Bardzo istotnym zagadnieniem jest cykl ży-cia MIDletu, który definiuje trzy stany, w któ-rych MIDlet może się znaleźć (Rysunek 1):

• zatrzymany (paused) – MIDlet jest zaini-cjowany i oczekuje;

• aktywny (active) – MIDlet funkcjonuje normalnie. Wejście w stan następuje po wywołaniu metody startApp();

• zakończony (destroyed) – MIDlet zwalnia wszystkie swoje zasoby i zostaje zakoń-czony. Wejście w stan następuje: kiedy metoda destroyApp() zwróci wyjątek błędnego argumentu i zrzucony zosta-nie wyjątek MIDletStateChangeException, kiedy metoda notifyDestroyed() wykona się pomyślnie. MIDlet musi naj-pierw wykonać metodę destroyApp() , a dopiero po niej notifyDestroyed().

Każdy MIDlet powinien posiadać implemen-tację trzech poniższych metod:

• startApp() – wywoływana po inicjali-zacji obiektów;

• pauseApp() – wstrzymuje działanie aplikacji, przygotowuje MIDlet do stanu zatrzymania i zwalnia zasoby;

• destroyApp() – przygotowuje MIDlet do zamknięcia. Zwolnione są zablokowane

wcześniej zasoby, w pamięci stałej powinny zostać zapisane informacje, które mają być dostępne przy ponownym uruchomieniu.

Co chcemy osiągnąć i czego do tego potrzebujemyNaszym celem jest utworzenie własnego progra-mu na komórkę – MIDletu służącego do wymia-ny walut. Najważniejszym wymaganiem dla na-szej aplikacji jest pobieranie aktualnych kursów walut ze strony Narodowego Banku Polskiego (NBP). Zakładamy, że nasz MIDlet będzie umoż-liwiał dokonywanie następujących konwersji:

• PLN (polski złoty) → EUR (euro);• EUR → PLN;• PLN → USD (dolar amerykański);• USD → PLN.

Oczywiście nasza aplikacja musi obsługiwać sytuacje wyjątkowe i błędne (np. wpisywanie liter zamiast cyfr w kwocie do przeliczenia).

Na stronie NBP w sekcji Informacja o termi-nach publikacji kursów walut NBP znajdują się informacje o zawartości poszczególnych do-stępnych tabel kursów. Dla ułatwienia przyj-miemy, że interesują nas średnie kursy walut zapisanych w tabeli A dostępnej pod adresem http://nbp.pl/kursy/kursya.html, która jest aktu-alizowana w każdy dzień roboczy w godzinach 11:45–12:15.

W związku z tym pojawia się dodatkowe wy-maganie polegające na informowaniu użytkow-nika dokonującego przeliczenia o dacie, z której pochodzą kursy walut. Aby przystąpić do pracy, musimy mieć zainstalowane na naszym kom-puterze następujące oprogramowanie:

• JAVA Software Development Kit (SDK) – jest to środowisko przeznaczone dla każ-dego, kto chciałby rozpocząć programowa-nie w języku Java. Zawiera w sobie pakiet Java Runtime Environment, a także dodat-kowe narzędzia takie jak kompilator i de-

JME na przykładzie

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.

Dowiesz się:• Co to jest J2ME;• Jak tworzyć i uruchamiać własne programy

na komórki.

Powinieneś wiedzieć:• Podstawy języka Java;• Podstawy języka HTML.

Poziom trudności

Przelicznik walut na podstawie kursów NBP

Page 149: SDJ Extra 34 Biblia

148

Programowanie JavaME

SDJ Extra 34 Biblia

JME na przykładzie

www.sdjournal.org 149

bugger. Darmowe środowisko pobierze-my z tej strony: http://java.sun.com/javase/ (z sekcji Download wybieramy update 6).

• JME Wireless Toolkit – zbiór narzędzi, któ-re dostarcza programistom środowisko emulujące, dokumentacje i przykłady po-zwalające na budowanie aplikacji w tech-nologii JME. Darmowy pakiet pobierze-my ze strony: http://java.sun.com/javame/index.jsp (z sekcji Download wybieramy Sun Java Wireless Toolkit 2.5.2 for CLDC).

• Dowolny edytor tekstowy, w którym bę-dziemy pisać kod naszej aplikacji. Dla uła-twienia dobrze byłoby, gdyby edytor pod-świetlał składnię języka Java. Przykłado-wym edytorem może być JCreator LE (do pobrania ze strony producenta: http://www.jcreator.com/)

Wireless Toolkit umożliwia użytkownikom tworzenie aplikacji z wykorzystaniem czte-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-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ć dla czytelników domyślnie dostępne przykłado-we MIDlety, na przykładzie których można za-obserwować możliwość tej technologii, a tak-że znajdować ciekawe rozwiązania i zdobywać wiedzę z tego obszaru.

Nowy MidletW głównym oknie Wireless Toolkit klikamy przycisk New project, a następnie wpisujemy na-zwę tworzonego MIDletu i nazwę głównej je-go klasy, np. PrzelicznikWalut. W oknie Set-tings for project, z menu Target platform, wybie-ramy JTWI, a następnie wybieramy konfigura-cję CLDC 1.1. Pozostałe opcje zostawiamy bez zmian i klikamy OK. W tym momencie w loka-lizacji C:\Documents and Settings\user_name\j2mewtk\2.5.2\apps (gdzie user_name to nazwa naszego użytkownika systemowego) utworzo-ny został katalog z taką samą nazwą jak nasz MI-Dlet zwierający następujące podkatalogi:

• Bin – spakowane pliki binarne (*.jar), pli-ki repozytorium (*.mdf, *.jad);

• Lib – dodatkowe biblioteki;• Res – zasoby, np. pliki graficzne, dźwię-

kowe;• Src – kody źródłowe MIDletu (*.java).

Finalna postać MIDletu składa się z co naj-mniej dwóch plików, tj. z pliku samego programu (PrzelicznikWalut.jar) i pliku re-pozytorium (PrzelicznikWalut.jad), któ-ry zawiera informacje dot. wersji MIDle-tu, autora, lokalizacji programu, jego zaso-by i nazwę.

Kolejnym krokiem, jaki musimy zrobić, jest utworzenie w katalogu Src pliku o nazwie PrzelicznikWalut.java, w którym zapiszemy kod źródłowy naszego programu.

Budowa interfejsuW pierwszym kroku musimy zaimportować odpowiednie biblioteki. Bardzo ważne jest, że w JME nie mamy dostępu do istniejących

w J2SE pakietów graficznych, tj. Swing czy AWT. Mamy zaś dostęp do klas:

• java.io – obsługa strumieni wejścia/wyjścia;• java.lang – podstawowe klasy typu

String (tak jak w J2SE);• java.util – klasy użytkowe dla specjal-

nych struktur danych (tak jak w J2SE).

Ponadto w JME dostępne są dodatkowe pa-kiety:

• javax.microedition.midlet – zawiera jed-ną klasę o nazwie MIDlet, która definiu-je interakcje między aplikacjami a śro-dowiskiem uruchomieniowym, klasa ta musi rozszerzać klasę główną (extends MIDlet) w każdym programie, co ozna-cza przejęcie jej interfejsu;

• javax.microedition.lcdui – zawiera klasy UI API (User Interface API), wykorzysty-

Rysunek 1. Cykl życia midletu

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

������

������

���������

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

����������

����������

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

Listing 1. Szkielet klasy PrzelicznikWalut

import java.io.*;

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

import javax.microedition.io.*;

public class PrzelicznikWalut extends MIDlet implements CommandListener{

public PrzelicznikWalut()

{

// konstruktor

}

public void startApp()

{

// kod wykonywany przy starcie oraz przy wznowieniu

}

public void pauseApp()

{

// kod wykonywany przy wstrzymaniu

}

public void destroyApp (boolean unconditional)

{

// kod wykonywany przy zamykaniu

}

}

Page 150: SDJ Extra 34 Biblia

150

Programowanie JavaME

SDJ Extra 34 Biblia

JME na przykładzie

www.sdjournal.org 151

wane do budowania interfejsu użytkow-nika tworzonych MIDletów;

• javax.microedition.io – zawiera klasy wspomagające sieć oparte na konfigura-cji z ograniczeniami (CLDC);

• javax.microedition.rms – dostarcza aplika-cjom możliwość zapisywania informacji przez użytkownika, ich przechowywania i odtwarzania. Ten prosty system bazoda-nowy nazywamy Record Managment Sys-

tem (RMS) (nie będziemy wykorzystywać tego pakietu w naszej aplikacji).

Po zaimportowaniu odpowiednich pakietów przyszedł czas na deklarację klasy i obowiąz-kowych metod. Pamiętajmy, że nazwa głów-nej klasy musi być taka sama jak nazwa pli-ku z kodem źródłowym programu. W tym momencie kod naszego programu jest taki jak na Listingu 1. Zauważmy, że w defini-cji klasy PrzelicznikWalut dodaliśmy obo-wiązkowy parametr extends MIDlet, a tak-że informację o własnej implementacji inter-fejsu CommandListener, co pozwoli nam na obsługę dodawanych elementów, np. reak-cję na przyciśnięcie przycisków funkcyjnych w programie.

Kolejnym krokiem jest zdefiniowanie wy-glądu naszego programu. Definiujemy nastę-pujące elementy i zmienne:

• zmienna typu Display – umożliwi odwo-łanie do ekranu telefonu komórkowego, co da nam możliwość wyświetlania na nim przygotowanych przez nas formularzy;

• trzy zmienne typu Form – formularze wy-świetlane na ekranie telefonu; pierwszy będzie zawierał elementy do wprowadza-nia kwoty do przeliczenia oraz typu prze-liczenia, drugi formularz będzie służył do wyświetlenia wyniku przeliczenia, trzeci zaś do wyświetlenia komunikatu oczeki-wania na przeliczenie kwoty;

• zmienna typu Ticker – umożliwi wy-świetlenie na ekranie przewijającego się napisu z nazwą programu (zmienna typu String);

• pole tekstowe TextField – służące do wpisywania przeliczanej kwoty. Zwróć-my uwagę, że polu temu przypisujemy właściwość TextField.DECIMAL, dzięki czemu zagwarantujemy sobie możliwość wpisania tylko liczb i znaku kropki;

• zmienna typu ChoiceGrup – stanowić będzie listę dostępnych typów konwersji walutowych;

Listing 2. Definicja obiektów, zmiennych

/**Ekran komorki**/

private Display mDisplay;

/**Formularz do wprowadzania danych**/

private Form wartosc_form;

/**Formularz do wyświetlania wyników**/

private Form wynik_form;

/**Formularz oczekiwania na wynik**/

private Form waitForm = new Form("Waiting...");

/**Przewijana nazwa programu**/

private static final String TICKER_TEXT = "Przelicznik walut - igorkruk.pl";

private Ticker t = new Ticker(TICKER_TEXT);

/**Pole tekstowe do wpisywania przeliczanej wartosci**/

private final TextField wartosc = new TextField("Wpisz kwotę: ", "",

10,TextField.DECIMAL);

/**Lista mozliwych przelicznikow**/

private final ChoiceGroup cg = new ChoiceGroup("", ChoiceGroup.POPUP,

new String[] {"EUR->PLN", "PLN->EUR", "USD->PLN", "PLN->USD"}, null);

/**Polecenie wyjscia z aplikacji**/

private final static Command CMD_EXIT = new Command("Exit", Command.EXIT, 1);

/**Polecenie przejscia do wczesniejszego formularza**/

private final static Command CMD_BACK = new Command("Back", Command.BACK, 1);

/**Polecenie do pobrania aktualnego kursu walut**/

private final static Command CMD_CONNECT = new Command("Connect", Command.SCREEN,

1);

/**Jaka zamiana walut**/

int pozycja = 0;

/**Warotsc, ktora przeliczamy**/

private double ile_przeliczamy;

/**Kod waluty, ktora przeliczamy: EUR, USD**/

private String wybrana_waluta = "";//EUR czy USD

/**Adres aktualnego kursu **/

private String url = "http://nbp.pl/kursy/kursya.html";

Listing 3. Konstruktor i metoda startApp

public PrzelicznikWalut()

{

// konstruktor

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

Page 151: SDJ Extra 34 Biblia

150

Programowanie JavaME

SDJ Extra 34 Biblia

JME na przykładzie

www.sdjournal.org 151

• trzy zmienne typu Command – będą od-powiadały za przyciski funkcyjne na ekranie komórki; odpowiednio: wyjście z programu (CMD _ EXIT), przejście do wcześniejszego formularza (CMD _ BACK), połączenie ze stroną NBP i przeliczenie wpisanej kwoty (CMD _ CONNECT);

• dodatkowe zmienne zawierające m.in. ad-res tabeli z kursami walut na stronie NBP, przechowującej wpisaną wartość do prze-liczenia.

Definicja wszystkich opisanych wyżej zmiennych zawarta została na Listingu 2.

W kolejnym kroku musimy zdefiniować, które elementy będą znajdować się na formu-larzach, a następnie podać formularz starto-wy dla naszego programu. W tym celu w kon-struktorze naszej klasy PrzelicznikWalut de-finiujemy wartość zmiennej wartosc_form, do której przypisujemy także obiekty typu Ticker (przewijany tekst). Następnie przy pomocy metody append umieszczamy na for-mularzu wartosc_form obiekt do wpisywa-nia kwoty (wartosc, TextField) oraz rozwi-janą listę z dostępnymi przelicznikami (cg, ChoiceGroup). Na tym formularzu powinny być dostępne dwa przyciski funkcyjne: pierw-szy powinien służyć do wyjścia z programu, drugi do uruchamiania procedury przelicza-nia walut. W związku z tym przy pomocy metody addCommand dodajemy dwa zadekla-rowane wcześniej przyciski: CMD_EXIT, CMD_CONNECT. Aby program obsługiwał kliknięcie w te przyciski, musimy przypisać do formula-rza nasłuch, który zdefiniujemy w dalszej czę-ści artykułu. Wykonujemy to poprzez metodę setCommandListener(this).

Kiedy mamy już przygotowany odpowied-ni formularz, możemy wyświetlić go na ekra-nie telefonu komórkowego. W tym celu w metodzie startApp() definiujemy obiekt mDisplay, który będzie stanowić odwołanie do głównego ekranu komórki (Display.get-Display(this);), a następnie wyświetla-

my na nim formularz wartosc_form (mDi-splay.setCurrent(wartosc_form);) Defini-

cję konstruktora oraz metody startApp() za-wiera Listing 3.

Listing 4. Implementacja interfejsu CommandListener

public void commandAction(Command c, Displayable d) {

//jesli jestesmy na formie wpisywania informacji i naciskamy connect

if ((d.equals(wartosc_form)) && (c==CMD_CONNECT))

{

String ile = wartosc.getString();

try

{

//zamieniamy string na double

ile_przeliczamy = java.lang.Double.parseDouble(ile);

}

catch (NumberFormatException e) {}

//ustawiamy nowa forme na czas laczenia i pobierania kursu

mDisplay.setCurrent(waitForm);

try

{

//pobieramy jaki ma byc przelicznik

switch (cg.getSelectedIndex())

{

case 0: pozycja=1; break;

case 1: pozycja =2; break;

case 2: pozycja=3; break;

case 3: pozycja =4; break;

default: pozycja =1;

}

//sprawdzamy cz interesuje nas kurs EUR czy USD

if ((pozycja==1) || (pozycja==2))

{

wybrana_waluta = "EUR";

}

else

{

wybrana_waluta = "USD";

}

}

catch (NumberFormatException e) {return;}

//pobieranie kursu zestrony NBP.pl w oddzielnym watku

Thread t = new Thread()

{

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

Page 152: SDJ Extra 34 Biblia

152

Programowanie JavaME

SDJ Extra 34 Biblia

JME na przykładzie

www.sdjournal.org 153

Implementacja nasłuchu CommandListenerKolejną rzeczą, jaką musimy zrobić, jest za-implementowanie interfejsu nasłuchu CommandListener. Jeśli jesteśmy aktualnie na formularzu służącym do wpisywania kwoty i

wybrania typu przeliczenia i nastąpi kliknię-cie przycisku CONNECT, wówczas:

• program sczytuje wpisaną wartość i par-suje ją do zmiennej typu Double;

• wyświetla na ekranie formularz oczeki-

wania na przeliczenie (w tym czasie na-stąpi połączenie ze stroną NBP i przeli-czenie wartości);

• sprawdza, jaka waluta obca (euro lub do-lar amerykański) została wybrana;

• tworzy nowy wątek (obiekt typu Thread) i w definicji jego metody run wywołuje funkcję connect() z adresem zawierającym tabelę z kursami walut;

• uruchamia zadeklarowany wątek.

Jeśli jesteśmy na formularzu z wynikiem prze-liczenia i klikniemy przycisk BACK, wówczas program powinien przenieść nasz formularz służący do wpisywania danych. Z kolei jeśli klikniemy przycisk EXIT, to program jest za-mykany. Pełna definicja implementacji nasłu-chu przedstawiona została na Listingu 4.

Połączenie do strony NBPZa pobranie kursów walut ze strony NBP odpo-wiadać będzie funkcja connect(), która przyj-muje jeden parametr – adres url. Sposób działa-nia tej funkcji jest niezwykle prosty. Przy pomo-cy obiektu HTTPConnection łączy się ze wska-zanym adresem, do którego podpina strumień wejściowy, z którego będziemy czytać dane. Na-stępnie w pętli sczytywane są pojedyncze war-tości (int), które są konwertowane na typ char – znakowy, i dodawane do bufora. Na koniec 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-mi walut znak po znaku. Na koniec wywoły-wana jest funkcja pobierz_kurs(), do której przekazujemy pobraną stronę HTML i wybraną wcześniej walutę obcą. Treść funkcji connect() zawarty został na Listingu 5.

Pobranie kursu walutyAby zrozumieć zasadę działania funkcji pobierz_kurs(), najlepiej jest zacząć od otwarcia strony z tabelą kursów w przeglą-darce internetowej. Jeśli zapoznamy się te-raz ze źródłem tej strony, to zauważymy, że jej zasadniczą i najbardziej nas interesują-cą częścią jest tabela HTML, w której każdy wiersz odpowiada oddzielnej walucie obcej. Nas w szczególności będą interesować druga i trzecia kolumna: nazwa waluty i jej aktual-ny kurs. Warto zwrócić jeszcze uwagę, że nad tabelą znajduje się informacja, z którego dnia pochodzą poniższe kursy walut. Nasza pra-ca w zakresie funkcji pobierz_kurs() po-legać będzie zatem na odpowiednim wycię-ciu dwóch wartości: daty i interesującego nas kursu (w zależności, czy użytkownik wybrał EUR lub USD , ta waluta będzie przez nas szu-kana w tabeli). Na koniec wywołujemy funk-cję przelicz(), która na podstawie przekaza-nych wartości: daty i kursu, przeliczy dla nas kwotę i wyświetli odpowiednie informacje na formularzu wynik_form. Listing 6 zawiera treść funkcji pobierz_kurs().

Listing 5. Definicja metody connectprivate void connect(String url)

{

HttpConnection hc = null;

InputStream in = null;

try {

HttpConnection c = null;

InputStream is = null;

StringBuffer b = new StringBuffer();

try {

c = (HttpConnection)Connector.open(url);

is = c.openDataInputStream();

int ch;

while ((ch = is.read()) != -1)

{

b.append((char) ch);

}

}

finally {

if(is!= null) {

is.close();

}

if(c != null) {

c.close();

}

}

String wynik_s = b.toString();

pobierz_kurs (wynik_s,wybrana_waluta);

}

catch (IOException ioe) {

}

Listing 6. Definicja metody pobierz_kurs

private void pobierz_kurs(String strona,String waluta)

{

String data = "";//data kursu

String kurs = "";

int pozycja_daty = strona.indexOf("z dnia <b>");

data = strona.substring(pozycja_daty+10,pozycja_daty+20);

//pobieramy kurs wybranej waluty

if (waluta.equals("USD"))

{

// wybralismy USD

int pozycja_waluty = strona.indexOf("USD");

kurs = strona.substring(pozycja_waluty+26,pozycja_waluty+32);

}

else

{

//wybralismy EUR

int pozycja_waluty = strona.indexOf("EUR");

kurs = strona.substring(pozycja_waluty+26,pozycja_waluty+32);

}

//zamieniamy , na .

String kurs_2 = kurs.substring(0,1) + "." + kurs.substring(2,kurs.length());

przelicz(kurs_2,data);

}

Page 153: SDJ Extra 34 Biblia

152

Programowanie JavaME

SDJ Extra 34 Biblia

JME na przykładzie

www.sdjournal.org 153

Przeliczenie kwoty i wyświetlenie wynikówW pierwszej kolejności w funkcji przelicz() sprawdzamy, który typ przeliczenia wybrał

użytkownik. W zależności od jego wyboru do-konywana jest odpowiednia operacja matema-tyczna – przeliczająca waluty oraz budująca od-powiedni komunikat końcowy wyświetlany dla

użytkownika. Do formularza wynik_form do-dawany jest element StringItem, który umożli-wia wyświetlenie dowolnego ciągu znakowego. W naszym przypadku jest to informacja o dacie, z której pochodzą wykorzystane w przeliczeniu kursy oraz wynik przeliczenia. Dodatkowo do formularza dodawane są przyciski CMD_BACK i CMD_EXIT. Na koniec formularz ten wyświetla-ny jest na ekranie telefonu komórkowego. Li-sting 7 zawiera treść funkcji przelicz().

Uruchomienie aplikacjiPrzyszedł czas na uruchomienie aplikacji. W tym celu zapisujemy treść naszej klasy i wraca-my do Wireless Toolkit. Z menu wybieramy Bu-ild, aby dowiedzieć się, czy nasza klasa zostanie poprawnie skompilowana. Jeśli nie popełniliśmy żadnego błędu, to powinniśmy otrzymać komu-nikat Build complete. Teraz możemy urucho-mić aplikację, wybierając z menu Run. Na Ry-sunkach 2 i 3 przedstawiłem zrzuty ekranowe z naszego MIDletu. Warto zaznaczyć, że w mo-mencie kliknięcia przycisku CONNECT aplika-cja zapyta się nas, czy wyrażamy zgodę na połą-czenie naszej aplikacji z Internetem (stroną NBP z kursami walut). Musimy kliknąć przycisk Yes. Oczywiście należy także upewnić się, czy ewen-tualnie zainstalowane na naszym komputerze oprogramowanie Firewall zezwala na połączenie aplikacji Wireless Toolkit z Internetem.

PodsumowanieW artykule tym przedstawiłem architektu-rę technologii JME i zasady tworzenia MIDle-tów. Dzięki prostej, lecz w pełni funkcjonal-nej aplikacji czytelnik miał możliwość zapo-znania się z możliwościami, jakie daje tech-nologia JME. Oczywiście, część z przedsta-wionych rozwiązań jest stworzona tylko na potrzeby artykułu i ze względu na ogranicze-nia w jego objętości – odpowiednio uprosz-czona i skrócona. Najważniejszą zmianą, do której zachęcam zainteresowanych czytelni-ków, jest napisanie oddzielnego WebService, który odpowiedzialny byłby za ściąganie kur-sów walut ze strony NBP w formacie XML, a nie, jak zaproponowałem, w HTML. Dzięki temu moglibyśmy rozszerzyć możliwości MI-Dletu o przeliczanie innych walut. Mam na-dzieję, że przedstawione w artykule informa-cje zachęcą czytelników do rozpoczęcia przy-gody z pisaniem własnych programów na ko-mórkę.

Listing 7. Definicja metody przelicz

private void przelicz(String kurs,String data)

{

double wynik = 0;

String tekst = "\n";

double kurs_d = Double.parseDouble(kurs);

if (pozycja==1)

{

//EUR-PLN

wynik = ile_przeliczamy*kurs_d;

String pom = ""+wynik;

pom = pom.substring(0,pom.indexOf(".")+3);

tekst += ""+ile_przeliczamy+" EUR = "+pom+" PLN";

}

if (pozycja==2)

{

//PLN-EUR

wynik = ile_przeliczamy/kurs_d;

//dodatkowa zmienna aby obciac wynik tylko do 2 miejsc po przecinku

String pom = ""+wynik;

pom = pom.substring(0,pom.indexOf(".")+3);

tekst += ""+ile_przeliczamy+" PLN = "+pom+" EUR";

}

if (pozycja==3)

{

//USD-PLN

wynik = ile_przeliczamy*kurs_d;

String pom = ""+wynik;

pom = pom.substring(0,pom.indexOf(".")+3);

tekst += ""+ile_przeliczamy+" USD = "+pom+" PLN";

}

if (pozycja==4)

{

//PLN-USD

wynik = ile_przeliczamy/kurs_d;

//dodatkowa zmienna aby obciac wynik tylko do 2 miejsc po przecinku

String pom = ""+wynik;

pom = pom.substring(0,pom.indexOf(".")+3);

tekst += ""+ile_przeliczamy+" PLN = "+pom+" USD";

}

//przygotowujemy forme dla wyniku

wynik_form = new Form("WYNIK");

StringItem instrukcje = new StringItem("Wg. KURSU "+wybrana_waluta+" NBP z dnia

"+data+": ",tekst);

//dodajemy tekst wyniku

wynik_form.append(instrukcje);

//przycisk BACK - cofa nas to formy wprowadzania informacji

wynik_form.addCommand(CMD_BACK);

//przycisk EXIT - wyjscie z aplikacji

wynik_form.addCommand(CMD_EXIT);

//dodanie nasluchu

wynik_form.setCommandListener(this);

//ustawiamy aktualna forme na forme wyniku

mDisplay.setCurrent(wynik_form);

}

IGOR KRUKIgor 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: [email protected], http://www.igorkruk.pl

Page 154: SDJ Extra 34 Biblia

154

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 155

Nie ma chyba użytkownika kompu-tera wywodzącego się z tak zwa-nej starej szkoły (ang. old school),

który nie miałby na swoim koncie wielu go-dzin spędzonych na grze w klasyczną plat-formówkę. Ten gatunek gier przewodził kiedyś wszystkim produkcjom. Gry plat-formowe, potocznie nazywane jump & run, wciąż mają w sobie coś magicznego, co spra-wia, że wracamy do nich z wielkim senty-mentem. Warto zatrzymać się nad tym kla-sycznym gatunkiem i zajrzeć w proces two-rzenia tego typu gier od kuchni. Na ryn-ku dostępna jest bardzo duża ilość plat-formówek na telefony komórkowe. Two-rzenie jump & run'a na urządzenia mobil-ne, gdzie grę trzeba dostosować do kilku-set telefonów i kilkudziesięciu rozdzielczo-ści, jest wbrew pozorom dość prostym pro-cesem, przynajmniej w porównaniu do in-nych typów gier. Przede wszystkim dzięki ogromnej skalowalności proces portowania (patrz: Ramka Portowanie aplikacji JME) jest znacząco krótki. Oznacza to oczywi-ście zarówno tańsze koszty produkcji, jak i potencjalne większe zyski. Drugą i chy-

ba najważniejszą zaletą platformówek, pa-trząc z punktu widzenia programisty, jest możliwość wykorzystania raz zaimplemen-towanego silnika tego typu gier przy pro-dukcji kolejnych tytułów z tego gatunku. Tworzenie nowej gry sprowadza się w ta-kim przypadku jedynie do podmiany zaso-bów, tj.: grafiki, animacji, dźwięków, plansz itp. Drobne modyfikacje fabuły, dodanie kilku nowych elementów funkcjonalności, i mamy nowy tytuł! Co ciekawe, opisany wyżej proces może w wielu przypadkach odbyć się bez konieczności ingerencji pro-gramisty. Niestety, są też negatywne stro-ny takiego podejścia. W platformówkach testowanie i znajdowanie błędów jest bar-dzo czasochłonne, zaś reprodukcja wyjąt-kowych sytuacji w grze trudna. Początko-wy etap produkcji wymaga przygotowania silnika do renderowania planszy, logiki gry, sztucznej inteligencji przeciwników, inte-rakcji między postaciami w grze. Nieste-ty potrzeba na to dużo czasu, a wysokiej ja-kości silnik mamy najczęściej dostępny do-piero po wydaniu kilku udanych produkcji. Mimo wszystko praca nad takim klasykiem daje ogromną satysfakcję. Chęć znajdywa-nia kolejnych kluczy, monet, kryształów, diamentów, ukrytych poziomów, a także (a może przede wszystkim) walka z przeciw-nikami, którzy przeszkadzają osiągnąć cel, wciąga bez pamięci.

W ramach niniejszego artykułu napisze-my prostą grę platformową na telefony ko-mórkowe oferujące wsparcie dla standardu JME. Naszą pracę podzielimy na kilka ko-lejnych etapów. Zaczniemy od doboru od-powiednich narzędzi oraz wymyślenia fa-buły. W kolejnym etapie naszej pracy sku-pimy się na przygotowaniu tzw. poziomu (ang. level), czyli – innymi słowy – na stwo-rzeniu wirtualnej rzeczywistości, w któ-rej będzie egzystował nasz bohater. Kie-dy będziemy już mogli oglądać stworzony przez nas świat na ekranie telefonu komór-kowego, ożywimy bohatera, tak aby stero-wany przez gracza poruszał się po utwo-rzonej wcześniej planszy. Aby życie nasze-go wojownika nie było zbyt proste, doda-my w grze przeciwników oraz wzbogaci-my ich prymitywną inteligencją. Dodawa-nie bonusów, dodatkowych punktów i cza-su to nieodzowne elementy każdej platfor-mówki, w związku z czym nie może ich za-braknąć w naszej mini-produkcji. W dal-szej kolejności dodamy obsługę punktów i upływającego czasu oraz prosty interfejs użytkownika (ang. user interface). Na ko-niec pozostawimy nieodzowną część pro-cesu produkcji każdej gry: testy, testy, te-sty. Na tym etapie zalecane będzie wspar-cie brata, siostry lub kuzyna; niech grają i dzielą się wszelkimi spostrzeżeniami. To wbrew pozorom jeden z ważniejszych eta-pów tworzenia gry, tzw. testy grywalności (ang. play testing). Na podstawie wyników tych testów dopracowuje się szczegóły, po-prawia plansze, dobiera lepiej czas i usta-wienia bonusów, kolejnych żyć, a także po-zycje odpowiednich przeciwników na plan-szy. Nasza gra musi być grywalna, dlatego

Klasyczna platformówka na komórkę

Tworzenie każdej gry, nawet najprostszej, łączy w sobie wiele wyzwań, z którymi musi uporać się programista. Tworzenie aplikacji dla platformy mobilnej niesie dodatkowe problemy. Jednak satysfakcja z napisania własnej, nawet bardzo prostej gry na telefon komórkowy, jest bezcenna! Jeśli chciałbyś poczuć taką satysfakcję, to przeczytaj poniższy artykuł.

Dowiesz się:• W jaki sposób stworzyć prostą grę na tele-

fon komórkowy;• W jaki sposób można budować plansze do gry;

Powinieneś wiedzieć:• Podstawowa znajomość Javy.

Poziom trudności

Piszemy grę bazującą na platformie Java Micro Edition

Page 155: SDJ Extra 34 Biblia

154

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 155

każda uwaga wytrawnego gracza jest cenna i warto się zastanowić nad sugestiami i spo-strzeżeniami osób trzecich. Na koniec pod-sumujemy efekty naszej pracy, zastanowi-my się nad możliwościami dalszego rozwo-ju projektu oraz ulepszeniem i optymaliza-cją. Na ten moment nie pozostaje nic inne-go, jak tylko zabrać się do pracy.

Wybieramy narzędziaNasz projekt będziemy rozwijać z wyko-rzystaniem platformy Java, więc na począ-tek odwiedzimy strony firmy Sun i pobie-

rzemy pakiety Java SE Development Kit (JDK) w wersji 6 (lub nowszej). Bardzo ważne jest dobranie odpowiednich narzę-dzi do pracy. W przypadku naszego projek-tu zalecam wykorzystanie środowiska Net-Beans lub Eclipse. Ja osobiście stosowałem NetBeans z wtyczką do obsługi JME. Na-rzędzie to jest wygodne, ma duże możli-wości, a zarazem jest stosunkowo łatwe w instalacji i konfiguracji. Do pracy będzie-my potrzebować również emulatora telefo-nu komórkowego, który we wstępnym eta-pie naszych prac będzie zastępował docelo-we środowisko działania gry. Mamy tu dość duży wybór, począwszy od standardowego sun'owskiego WTK (Sun Java Wireless Tool-kit), po emulatory od konkretnych produ-centów telefonów: Nokia, Sony Ericsson, Motorola (patrz: Ramka W sieci). Ja wy-brałem Sony Ericsson'a, jako że pokrywa on dużą ilość modeli, jest przyjazny i moż-

na go bardzo łatwo zintegrować z IDE. Ba-zowym modelem telefonu komórkowego, na którym będziemy pracować, będzie SE w900i. Rozdzielczość ekranu na tym urzą-dzeniu wynosi 240 pikseli szerokości i 320 pikseli wysokości. SE w900i oferuje rów-nież sporą ilość pamięci, ponad 3,7 MB he-ap'a, z czego ponad 2MB pamięci graficz-nej, natomiast ograniczeniem dla pliku wy-nikowego jest jedynie dostępna pamięć na karcie (470 MB).

Kolejnym bardzo ważnym narzędziem jest edytor plansz. Oczywiście można po-kusić się o napisanie własnego narzędzia. My jednak nie mamy na to czasu i skorzy-stamy z darmowego produktu. Jest spo-ro takich narzędzi; ja osobiście polecam TileStudio albo Mappy. Na nasze potrze-by w zupełności wystarczy Mappy: jest on bardzo prosty w obsłudze i ma podstawo-wą funkcjonalność. Umożliwia tworzenie

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

Game DesignerProjektant 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.

Listing 1. Przykładowa implementacja kodowania RLE

try {

DataOutputStream out = new

DataOutputStream(

new FileOutputStream("level_

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

Page 156: SDJ Extra 34 Biblia

156

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 157

kilku warstw, co jest ważne dla naszej gry. Przyda się jeszcze jakiś program do edy-cji grafiki, np. Irfanview. Wszystkie opisa-ne wyżej narzędzia można pobrać bezpłat-

nie z sieci (patrz: Ramka W sieci). Teraz, kie-dy mamy już wszystko co potrzebne do pracy, czas sięgnąć po kartkę papieru i wymyślić na-szą platformówkę.

Tworzymy fabułę – prosty projekt gryNasza gra ma być przede wszystkim prosta. Nie będziemy więc tworzyć wyrafinowa-nego, skomplikowanego scenariusza, choć z drugiej strony – pokusimy się o odrobinę fantazji. Proponuję osadzić bohatera (ang. hero) naszej gry w wirtualnym świecie wie-kowych moczar, zapomnianych lasów, w którym, skacząc po platformach, będzie on musiał odnaleźć magiczne klucze ukryte w różnych miejscach planszy. Szukając klu-czy, będzie on zbierał złote dukaty i zdo-bywał dodatkowe punkty. Jak dobrze po-szuka, to znajdzie dodatkowe życie albo dodatkowy czas na odnalezienie brakują-cych kluczy. Przeciwnicy występujący na planszy będą starali się utrudnić mu wyko-nanie zadania. Nasz bohater będzie spoty-kał ich w różnych miejscach. Zachowanie przeciwników będzie stosunkowo proste. Poruszać się będą oni na planszy według określonych znaczników. Jeśli nasz boha-ter znajdzie się w pobliżu terytorium wro-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-wadzi do utraty jednego z żyć. Zakładamy 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, kiedy jesteśmy odpowiednio blisko, zbyt bliski kontakt z przeciwnikiem spowodu-je utratę energii naszego bohatera. Głów-nym celem gry jest zdobycie jak najwięk-szej ilości punktów, czyli zebranie mak-symalnej ilości monet, każdy znaleziony klucz to dodatkowe punkty. Tak więc na-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-ne za pomocą d-pada lub joysticka telefo-nu komórkowego. Alternatywnie wskaza-na jest możliwość grania za pomocą kla-wiatury numerycznej telefonu, pamiętaj-my jednak, że nie każdy telefon posiada ta-kową klawiaturę.

Poniżej przedstawione są klawisze funkcyj-ne naszej gry:

• d-pad / joystic – poruszanie się po plan-szy w poziomie, skok w górę oraz ude-rzenie;

• klawisz numeryczny 2 – skok w górę;• klawisz numeryczny 4 – poruszanie

w lewo;• klawisz numeryczny 6 – poruszanie

w prawo;• klawisz numeryczny 5 – uderzenie.

W grze przewidziane będą dwa typy prze-ciwników:

Listing 2. Przykładowa implementacja dekodowania RLE

try {

byte[] decompressedLevel = new byte[levelMap.length];

DataInputStream in = new DataInputStream(

new FileInputStream("level_compressed.dat"));

i = 0;

while (true) {

try {

int b = in.readUnsignedByte();

int count = 1;

int val = b;

int n = (b&0xff);

if ((n&128) == 128) {

count = n&127;

val = in.readUnsignedByte();

}

for (; count > 0; count--) {

decompressedLevel[i] = (byte)val;

i++;

}

}

catch (EOFException e) {

break;

}

}

//sprawdzamy poprawność danych po dekompresji

for (i = 0; i < decompressed.length; i++)

if (decompressedLevel[i] != levelMap[i])

System.out.println(„BLAD DANYCH");

}

catch (IOException e) {

e.printStackTrace();

}

Listing 3. Klasa startowa Start.java

public class Start extends MIDlet {

private Game game;

public void startApp() {

if (game == null) {

game = new Game(this);

} else {

game.showNotify();

}

}

public void destroyApp(boolean unconditional) {

}

public void pauseApp() {

game.hideNotify();

}

}

Page 157: SDJ Extra 34 Biblia

156

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 157

• TYP1 – czeka aż bohater pojawi się w je-go pobliżu, wówczas rozpoczyna poru-szanie w jego kierunku;

• TYP2 – porusza się od znacznika do znacznika, zmieniając kierunek swo-jego ruchu na zgodny z kierunkiem znacznika.

Dodatki występujące w naszej grze:

• serduszko – dodatkowe życie;• moneta – punkty w grze;• klepsydra – dodatkowy czas na ukoń-

czenie poziomu.

Edytor poziomów – tworzymy planszeKażda dobra platformówka to przede wszystkim dobra plansza, innymi sło-wy: świetna grafika, odpowiednio ułożo-ne platformy, niespodziewane tajemne przejścia, trampoliny, czarne dziury i nie-bezpieczne miejsca. Jak można się domy-śleć, przygotowanie dobrego poziomu nie jest wcale łatwym zadaniem. Plansza bar-dzo często to wielki duży obszar, kilkana-ście, a nawet kilkadziesiąt razy większy niż ekran naszego telefonu. Świat, w którym porusza się nasz bohater musi być odpo-wiednio duży, aby gra była ciekawa. Ponad-to kolejne plansze w grze powinny być co-raz trudniejsze, większe, a gracz musi mieć poczucie narastającej trudności i ciągle od-krywać coraz to nowe elementy. Pierw-sze poziomy zwykle służą nauce porusza-nia się, oraz zapoznaniem się z umiejętno-ściami i możliwościami bohatera. Idea bu-dowania planszy w platformówkach opiera się na używaniu jednego lub kilku różnych tilesetów, z których buduje się poziom. Ti-leset to zestaw unikalnych elementów gra-ficznych, np. 16x16 pikseli, które pozwala-ją budować plansze. Oczywiście grafik ma tutaj ogromne pole do popisu, aby przygo-tować takie tile (w polskiej terminologii ja-

ko odpowiednik angielskojęzycznego poję-cia tile stosuje się słowo kafelek), z których będzie można utworzyć maksymalnie róż-norodne plansze, platformy, elementy do-datkowe itd. Korzyści z korzystania tilese-tów są ogromne. Trudno jest wyobrazić so-bie sytuację, w której grafik przygotowuje gigantyczną planszę, którą wyświetlamy na telefonie. Tym bardziej, że mamy tutaj sporo ograniczeń, zwykle można wgrać do pamięci telefonu grafikę wielkości dwóch, trzech ekranów. Nawet gdyby zastosować takie podejście, to wynikowy poziom był-by bardzo mały. A gdzie pozostałe elemen-ty gry, bohater, przeciwnicy i inne...? Nale-ży pamiętać, że po wgraniu np. pliku gra-ficznego 100x100 pikseli, który na dysku twardym zajmuje załóżmy 2kB, w pamię-ci telefonu zajmuje około 20kB. Co gorsza, zdarzają się urządzenia, na których będzie on zajmował około 40kB. Przy dostępnych dzisiaj nośnikach o pojemnościach liczo-nych w gigabajtach czy terabajtach, przy-toczone tu liczby wydają się być śmieszne. Jednak są jeszcze telefony na rynku, któ-re dysponują niewielką pamięcią graficz-ną. Telefony z tak zwanej listy 64k, czyli te, na których rozmiar uruchamianej pacz-ki *.jar nie może przekroczyć 65535 baj-tów, oferują swoim programom jedynie około 250kB pamięci (ang. heap memory), z czego 70kB to pamięć graficzna. W takiej sytuacji budowanie planszy z tili jest więc koniecznością. Tworzenie własnej planszy daje też bardzo dużo satysfakcji. Jest wiele dostępnych narzędzi do przygotowywania świata z tilesetów, często programiści przy-gotowują swoje własne edytory. My sko-rzystamy z pobranego wcześniej Mappy-

Win32. Do szczęścia brakuje nam jeszcze zestawu tili. Jeśli ktoś ma ochotę, to zachę-cam do pobawienia się w PhotoShopie, tu-dzież innym programie graficznym, i przy-gotowanie własnego zestawu. Jest to jed-nak dość czasochłonne i wymaga talentu artystycznego. My poszukamy w sieci goto-wego, darmowego tilesetu. Mnie udało się takowy znaleźć (patrz: Ramka W sieci). Na tej samej stronie autor udostępnia jeszcze kilka innych przydatnych grafik; amatorów tworzenia gier zachęcam do zapoznania się z tymi materiałami. Tileset do używania w Mappym musi być zapisany w formacie BMP, dlatego też pobraną grafikę (dostęp-ną w formacie PNG) przekonwertujemy przy pomocy IrfanView do postaci wyma-ganej przez nasze narzędzie.

Skoro mamy wszystko, co potrzeba, uru-chamiamy Mappy'ego. Program ten jest ła-twy i intuicyjny w obsłudze. Na początek wybieramy opcję File>New Map (skrót Ctr-l+M). Podajemy rozmiar pojedynczego ti-la 16 pikseli, oraz rozmiar całej mapy w tilach. Pierwszy poziom nie powinien być zbyt duży, powiedzmy 40 na 30 tili, co da-je nam w wyniku planszę o rozmiarach 640 na 480 pikseli. Proponuje włączyć jeszcze jedną opcję: MapTools>Dividers; w okien-ku zaznaczamy Enable Dividers, wybiera-my kolor siatki na niebieski: Line Colour: 0x0000ff, oraz ustawiamy rozmiar poje-dynczej kratki: Pixel gap X/Y na 16, czyli ta-ki jak rozmiar naszego tila. Wgrywamy nasz tileset z dysku (File>Import). Teraz możemy rozpocząć projektowanie naszego wirtual-nego świata. Ja, przyznam uczciwie, posze-dłem na łatwiznę i skorzystałem z przykła-dowego poziomu, pobranego wraz z tilese-

Listing 4. Fragment pliku z zasobami Res.java

public interface Res {

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

Page 158: SDJ Extra 34 Biblia

158

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 159

tem. Warto w tym momencie wspomnieć o jednej z użytecznych funkcjonalności, jakie daje Mappy; mowa tu o tworzeniu mapy z obrazka. Możemy wczytać wcześniej przy-gotowaną dużą mapę do programu, któ-ry sam podzieli ją na kafelki o zdefiniowa-nej wielkości oraz stworzy listę niepowta-rzalnych tili, które można wyeksportować. Istotne w tym przypadku jest, aby wielkość wczytywanej grafiki pokrywała się z wiel-kością naszej mapy, oraz aby była przygoto-wana tak, że da się podzielić na powtarzal-ne elementy.

Gdy już mamy mapę, czas wyeksporto-wać ją w takiej postaci, aby dało się ją od-tworzyć na telefonie komórkowym. W tym celu wybieramy File>Export as text... W okienku ustawiamy parametry eksportu.

Najprościej wygenerować mapę jako dwu-wymiarową tablicę, a wartości w tej tablicy to indeksy umieszczonych na mapie kafel-ków. Możemy również wyeksportować so-bie nasze tile. Najkorzystniej będzie usta-wić je w pojedynczą, długą kolumnę. Pozo-staje nam jeszcze ustalić, które tile z nasze-go tilesetu będziemy traktować jako kolizyj-ne, to znaczy takie, po których nasz boha-ter będzie mógł się poruszać (będą one sta-nowiły dla niego podłoże). W naszym ze-stawie są to wszystkie kafelki posiadają-ce elementy trawy – (indeksy 24-27, 32, 37, 46) oraz te, z których tworzymy po-ziome odgałęzienia od pni drzew (29-31, 34-35, 42).

Poziom wyeksportowany w postaci dwu-wymiarowej tablicy bajtów gotowy jest do

użycia bezpośrednio w kodzie źródłowym. Gdy mamy jednak do czynienia z większą liczbą poziomów, a tak zazwyczaj bywa w praktyce, zalecane jest, aby przenieść ich dane do zewnętrznych plików binarnych, które będą kolejno wczytywane w zależ-ności od potrzeby. W takiej sytuacji warto również pokusić się o prostą kompresję. Po-mimo tego, że pliki binarne z danymi po-ziomów bardzo dobrze się kompresują i w wynikowym rozmiarze pliku *.jar nie za-uważymy dużej różnicy, to po rozpakowa-niu naszej gry spora ilość nieskompresowa-nych plików będzie zajmować dużo miej-sca. Pisząc grę w JME, należy pamiętać, że warto walczyć o każdy kilobajt! Przygląda-jąc się naszemu plikowi binarnemu, łatwo zauważyć, że jest tam bardzo dużo powta-rzających się sekwencji. Bardzo prosta do implementacji jest kompresja RLE (ang. Run-Length Encoding), czyli kodowanie dłu-gości serii. Co więcej, RLE będzie bardzo efektywna w przypadku naszych danych. Kompresja ta polega na opisywaniu ciągu tych samych wartości za pomocą licznika powtórzeń. W ten sposób powstają pary (licznik, wartość).

Na przykład, zamiast sekwencji: 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,... mamy parę: (29,1). Przy-kładową implementację kodowania i dekodo-wania RLE przedstawiono odpowiednio na Listingach 1 i 2.

Na tym etapie zakończymy pracę z edyto-rem; wrócimy do niej przy dodawaniu prze-ciwników i rozmieszczaniu dodatkowych ele-mentów na planszy.

Struktura projektu, zaczynamy pisać kod!Rozpoczynając projekt gry na komórkę, ba-zujący na platformie JME, dobrze jest przy-jąć pewne założenia co do architektury tworzonej aplikacji. Przede wszystkim na-leży pamiętać, iż nie ma tutaj mowy o pi-saniu czysto obiektowym. Dobrą praktyką jest minimalizowanie ilości klas. Najbez-pieczniej jest zamknąć główny silnik gry w jednej klasie, do tego dodać jakieś dwie, trzy klasy pomocnicze do animacji, rysowa-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

Listing 5. Wyznaczenie obszaru renderowania

public void initLayer()

{

levelWidthT = LEVEL_WIDTH;

levelHeightT = LEVEL_HEIGHT;

//wielkosc planszy w pikselach

levelWidth = levelWidthT<<TILE_SHIFT;

levelHeight = levelHeightT<<TILE_SHIFT;

levelMaxOff = levelWidthT*levelHeightT;

levelX = 0;

levelY = 0;

screenWidthT = X_RES >> TILE_SHIFT;

screenWidth = screenWidthT<<TILE_SHIFT;

if(screenWidth < X_RES) {

screenWidth += TILE_SIZE;

screenWidthT++;

}

screenWidth += TILE_SIZE;

screenWidthT++;

screenHeightT = Y_RES >> TILE_SHIFT;

screenHeight = screenHeightT << TILE_SHIFT;

if(screenHeight<Y_RES) {

screenHeight += TILE_SIZE;

screenHeightT++;

}

screenHeight += TILE_SIZE;

screenHeightT++;

}

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

Page 159: SDJ Extra 34 Biblia

158

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 159

albo javowy interfejs (interface), imple-mentowany przez dowolną klasę. Jeśli zaj-dzie potrzeba edycji stałych, będziemy mieli łatwy i szybki dostęp do nich. Od-radzam stosowanie skomplikowanych hie-rarchii dziedziczenia, zachęcam do pro-stoty, zarówno na poziomie struktury klas, jak i kodu. Punktem startowym naszej apli-kacji będzie klasa Start, dziedzicząca po MIDlet. Upraszczamy ją do maksimum, pa-miętając, że nie będzie ona optymalizowa-na. Cały silnik gry zawarty będzie w klasie Game, zaś klasy pomocnicze to: LevelLayer (wyświetlanie planszy), Character (repre-zentacja bohatera oraz przeciwników). Do-datkowo stworzymy interfejs konfigura-cyjny ze stałymi (Config.java) oraz z za-sobami graficznymi (Res.java). Posiada-nie narzędzi do zarządzania zasobami w grze jest bardzo korzystne i znacząco uła-twia pracę przy ich dodawaniu i edytowa-niu. Na potrzeby naszej prostej gry wystar-czy, jeśli umieścimy w pliku Res.java tabli-cę nazw plików graficznych, stałe typu pu-blic final static int z offsetami do tej tablicy oraz tablicę obiektów typu Image[], w któ-rej będziemy przechowywać wszystkie gra-fiki. Listing 4 przedstawia fragment imple-mentacji interfejsu Res.

Skoro struktura klas jest już ustalona, tworzymy nowy projekt w naszym IDE, oraz ustawiamy kolejne właściwości dla projektu. W przypadku NetBeans działa-my jak następuje: w zakładce Platform wy-bieramy Manage Emulators... i podpina-my nasz emulator Sony Ericsson'a, postę-pując zgodnie z poleceniami. Jak wszyst-ko zrobimy poprawnie, to mamy możli-wość wybrania modelu telefonu z rozwi-janego pola Device. Wybieramy SonyErics-son_W900_Emu. Następna właściwość to już parametry midletu (aplikacji na tele-fon komórkowy), a dokładniej mówiąc, pa-rametry pliku JAD oraz Manifest. W Attri-butes musimy podać MIDlet-Name (nazwa aplikacji, np. Platformowka), MIDlet-Ven-dor (nazwa sprzedawcy/dostawcy, np. SDJ) oraz MIDlet-Version (numer wersji gry, np. 1.0). Kolejna ważna właściwość aplikacji to MIDlets, podajemy tu nazwę projektu, nazwę klasy startowej oraz ścieżkę do iko-ny aplikacji, czyli odpowiednio: Platfor-

mowka, Start, /icon.png. Należy pamiętać o tym, żeby nie używać polskich znaków w nazwach. Część parametrów możemy bezpiecznie pominąć (np. API Permissions, itd.). W zakładce Libraries & Resources usta-wiamy ścieżki do naszych zasobów, wybie-rając Add folder. W zakładce Obfuscating ustalamy wysoki (ang. high) poziom tzw. obfuskacji, czyli zaciemniania kodu. Jest to bardzo ważne, gdyż proces obfuskacji zna-cząco redukuje rozmiar plików *.class, wy-generowanych w procesie kompilacji. Po-prawia się też wydajność kodu, usuwane są nieużywane metody i informacje o symbo-lach. W trakcie tego procesu upraszczane są również nazwy zmiennych (np. zmienna int levelMap[] będzie zmieniona na int a[]). W zakładce Creating JAR definiuje-my nazwy naszych plików wynikowych jad i jar na Platformowka.jad oraz Platformow-ka.jar. Wybieramy OK i na tym kończymy konfigurację właściwości projektu.

Wyświetlamy planszę na ekranie...Najwyższy czas zobaczyć jakieś efekty do-tychczasowej pracy na ekranie naszej ko-mórki. W tym celu napiszemy prosty sil-nik renderujący mapę naszego świata. W tym celu stworzymy klasę LevelLayer i na starcie dodamy do niej wyeksportowaną ta-blicę bajtów opisującą strukturę naszego poziomu. Interfejs klasy będzie zawierał metodę inicjującą initLayer() (patrz: Li-sting 5), w której wyliczymy, jaki obszar bę-dziemy renderować co ramkę (ilość tili do renderowania w poziomie i w pionie). Je-śli ekran naszego telefonu wynosi X_RES = 240 i Y_RES=320 pikseli, to musimy rende-rować odpowiednio 240/16 = 15+1 tili w poziomie oraz 320/16 = 20+1 tili w pionie. Obszar renderowania rozszerzamy w oby-dwu kierunkach o wartość 1, aby przewi-janie ekranu odbywało się płynnie. Ponie-waż rozmiar jednego tila jest potęgą dwój-ki, dzielenie można zastąpić przesunięciem bitowym TILE_SHIFT. Obszar renderowa-nia (screenWidth oraz screenHeight) wy-znaczamy więc odpowiednio za pomocą następujących wyrażeń: X_RES>> SHIFT +1 oraz Y_RES>> SHIFT +1. W kodzie będzie-my potrzebowali przechowywać wyznaczo-ny obszar renderowania zarówno w pikse-lach (screenWidth, screenHeight), jak i w kafelkach (screenWidthT, screenHeightT). Dobrą praktyką w pisaniu gier na komór-ki (i nie tylko!) jest wyliczenie pewnych wartości, które będziemy wykorzystywać często w kodzie po to, aby zminimalizo-wać maksymalną ilość obliczeń. Taką war-tością będzie na przykład maksymalny rozmiar tablicy z danymi naszej planszy: levelMaxOff, dlatego na początku metody initLayer() wyliczamy jego wartość.

Ekran będziemy przewijać za pomocą me-tod moveHorizontal() oraz moveVertical() (patrz: Listing 6), podając jako parametr wartość zmiennej delta, określającej, o ile pikseli chcemy przesunąć mapę. Metody te ustawiają zmienne levelX i levelY, czyli aktualną pozycję planszy (lewy górny róg). Główny kod renderujący zaimplementowa-ny jest w metodzie renderLayer() (patrz: Listing 7), w niej odrysowujemy interesują-cy nas fragment planszy. Zaczynamy od le-wej górnej krawędzi i przesuwamy się w pra-wo, kolejno odrysowując kafelek po kafelku. Gdy osiągniemy prawą krawędź, przecho-dzimy do kolejnej linii aż osiągniemy pra-

Listing 6. Obsługa przewijania planszy w poziomie i pionie

public int moveHorizontal(int aDelta) {

int r = 0;

levelX += aDelta;

if(aDelta<0) {

if(levelX<0) {

r = levelX;

levelX = 0;

}

}

else {

int s = levelX-(levelWidth-

screenWidth);

if(s>0) {

levelX = levelWidth-

screenWidth;

r = s;

}

}

return r;

}

public int moveVertical(int aDelta) {

int r = 0;

levelY += aDelta;

if(aDelta<0) {

if(levelY<0) {

r = levelY;

levelY= 0;

}

}

else {

int s = levelY-(levelHeight-

screenHeight);

if(s>0) {

levelY = levelHeight-

screenHeight;

r = s;

}

}

return r;

}

Rysunek 3. Obszar kolizyjny bohatera

Page 160: SDJ Extra 34 Biblia

160

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 161

wy dolny wierzchołek. Rysunek 2 przedsta-wia poglądowo kolejność rysowanych tili. Kafelki w naszym tilsecie ułożone są piono-wo jeden pod drugim, więc możemy ustawić tzw. klipa (tj. prostokąt obcinania) raz na ca-ły renderowany w danym momencie wiersz (X_RES * TILE_SIZE = 240 * 16). Mapa wyeksportowana z edytora jest dwuwymia-rową tablicą indeksów do poszczególnych ti-li. Jeśli chcemy wyświetlić kafelek o indek-sie 10, to wystarczy na osi Y przesunąć tilset o wartość TILE_SIZE * 10 = 160 pikseli, al-bo jeszcze lepiej 10 << TILE_SHIFT. W celu optymalizacji operacji dokonywanych na ti-lach zastępujemy mnożenie i dzielenie prze-sunięciem bitowym. W taki oto prosty spo-sób kafelek po kafelku rysujemy fragment

naszej planszy. Dodając jedną pełnoekrano-wą grafikę wyświetlaną na drugim planie, przesuwaną z inną prędkością niż prędkość planszy, możemy uzyskać dodatkowy efekt pseudo-paralaksy; jak taki efekt zaimplemen-tować, pokazane jest na początku metody renderLayer(). Bardzo przydatna będzie jeszcze prosta metoda do ustawiania pozy-cji poziomu setPos(x, y); metoda ta poka-zana jest na Listingu 8. Jeśli, dla przykładu, chcemy rozpocząć grę w punkcie określo-nym indeksami (100, 100) na naszej plan-szy, to wywołujemy setPos(100,100). Po ta-kim wywołaniu nasza pozycja będzie usta-wiona centralnie na ekranie telefonu.

Opisana powyżej implementacja silni-ka renderującego jest bardzo prosta, acz-

kolwiek zupełnie wystarczająca na potrze-by naszej gry. Należy jednak wspomnieć, że silnik ten można dodatkowo zoptymali-zować, tak aby renderowanie odbywało się na off-screenie. Bardziej skomplikowany spo-sób w skrócie oznacza, że mamy dodatko-wy bufor wielkości ekranu, a po przesunię-ciu planszy silnik miałby odrysowywać tyl-ko jeden rząd bądź kolumnę kafelków. Ten mechanizm renderingu jest dużo szybszy od prostej implementacji przedstawionej w niniejszym artykule, ale niestety – wymaga alokowania dodatkowej sporej ilości pamię-ci, co w przypadku gier pisanych na telefo-ny komórkowe może stanowić poprzeczkę nie do przeskoczenia. Drugim ogranicze-niem tego rozwiązania jest brak możliwo-ści ustawienia przeźroczystości na rendero-wanym buforze, co oznacza, że tile muszą być pełne i nie mogą mieć obszarów prze-źroczystych.

Dodajemy bohateraW kolejnym kroku tworzymy klasę Character, będzie ona charakteryzować naszego bohatera. W tej klasie umieszcza-my kod odpowiedzialny za kolizje, porusza-nie się po planszy, odtwarzanie odpowied-nich animacji i maszynę wszystkich sta-nów postaci. Pierwszym obiektem, który stworzymy dzięki tej klasie, będzie nasz he-ro, ale wykorzystamy ją również w celu re-prezentacji przeciwników występujących w grze. Najpierw spróbujmy określić pod-stawową maszynę stanów dla naszej posta-ci. Pierwszy stan będzie stanem spoczyn-ku, w którym postać stoi nieruchomo. Na-zwiemy ten stan STAND. Drugi i trzeci stan będą reprezentować poruszanie się posta-ci po planszy w poziomie, tj. w lewo bądź w prawo. Stany te nazwiemy odpowiednio MOVE_LEFT oraz MOVE_RIGHT. Pozostało jesz-cze poruszanie się postaci w pionie. Jed-nakże na naszej planszy nie przewidujemy występowania takich elementów jak drabi-ny czy liny do wspinania, a w zamian za to planujemy umieścić tam platformy, na któ-re będzie można wskoczyć. Dlatego też zde-finiujemy stan skoku: JUMP. Gdy postać na-potka na swojej drodze wroga, powinna roz-począć walkę. Dlatego kolejny stan nazwie-my FIGHT. Musimy także stworzyć stan, w którym tracimy energię i życie: HURT. Prze-łączanie stanów będzie odbywać się za po-mocą metody setState(int state), zmia-na stanu polegać będzie na ustawieniu od-powiedniej animacji postaci do odtworze-nia (patrz: Listing 9).

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-

Listing 7. Renderowanie tili na ekranie

public void renderLayer(Graphics g) {

//simply paralax'a

int x = X_RES - (levelX>>1)%X_RES;

int y = Y_RES - (levelY>>1)%Y_RES;

g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_LT);

g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_RT);

g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_LB);

g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_RB);

for(int ty=levelY>>TILE_SHIFT, py=-(levelY%TILE_SIZE);

py<screenHeight; py+=TILE_SIZE, ty++)

{

g.setClip(0, py, X_RES, TILE_SIZE);

for(int tx=levelX>>TILE_SHIFT, px=-(levelX%TILE_SIZE);

px<screenWidth; px+=TILE_SIZE, tx++)

{

g.drawImage(Game.imgGet(Res.IMG_TILESET),

px, py-(tileLevel[ty][tx]<<TILE_SHIFT), 0);

//DRAW MASK

switch( maskLevel[ty][tx] )

{

case MASK_COIN:

g.drawImage( Game.imgGet(Res.IMG_COIN),

px, py,0);

break;

case MASK_HEART:

g.drawImage( Game.imgGet(Res.IMG_HEART),

px, py,0);

break;

case MASK_KEY:

g.drawImage( Game.imgGet(Res.IMG_KEY),

px, py,0);

break;

case MASK_TIME:

g.drawImage( Game.imgGet(Res.IMG_TIME),

px, py,0);

break;

}

}

}

}

Page 161: SDJ Extra 34 Biblia

160

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 161

tod moveLeft(), moveRight() oraz jump() (patrz: Listingu 10). Będziemy rozpatry-wać dwa przypadki. Pierwszy z nich wy-stępuje, gdy postać znajduje się na pod-łożu (onGround = true); wówczas doda-jemy lub odejmujemy wartość zmiennej speed do wektora prędkości. Drugi przy-padek ma miejsce, gdy postać nie styka się z podłożem, np. podczas skoku lub spada-nia z platformy; wtedy poruszamy się o po-łowę wolniej, czyli dodajemy lub odejmuje-my speed / 2 do wektora prędkości. Żeby poruszanie było niezależne od czasu trwa-nia ramki, musimy dodawaną/odejmowaną 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-nych założeń co do siły skoku, najlepiej zro-bić to przed rozpoczęciem projektowania poziomów. Takie informacje trzeba uzgod-nić z projektantem planszy, aby uniknąć sy-

tuacji, w których nasz bohater nie będzie mógł ukończyć danego poziomu, ponieważ nie wskoczy na strategiczną platformę. Lub odwrotnie: platformy będą tak ustawione, że za pomocą kilku skoków uda się zakoń-czyć grę.

Tworząc obiekt klasy Character, do kon-struktora przekazujemy szerokość i wyso-kość postaci określone w pikselach. War-to zauważyć, że nie jest to rozmiar klat-ki animacji, ale definicja obszaru kolizyj-nego (tak zwanego collision box'a). Jak sa-ma nazwa wskazuje, obszar ten służy głów-nie do określania przestrzeni kolizyjnych.

Rysunek 3 przedstawia prostokąt kolizyj-ny dla naszego bohatera. Prostokąt ten ma rozmiar 25 na 50 pikseli. Dla porówna-nia, wielkość klatki animacji bohatera to 51 na 72 pikseli. Mając takie informacje, możemy wykrywać kolizje postaci z tilami na planszy. Będziemy to robić co ramkę w metodzie updateCollision() (patrz: Li-sting 11). Ograniczymy się do sprawdzania tylko jednego punktu kolizyjnego: od do-łu. Idea polega na sprawdzaniu odległości między środkiem collision box'a i środkiem tila, z którym aktualnie występuje kolizja. Pierwszy krok to sprawdzenie rodzaju ti-

Listing 8. Ustawianie pozycji planszy

public void setPos(int aX,int aY) {

aX -= X_RES>>1;

aY -= Y_RES>>1;

if(aX<0) aX = 0;

if(aY<0) aY = 0;

if(aX>levelWidth-screenWidth)

aX = levelWidth -

screenWidth;

if(aY>levelHeight-screenHeight)

aY = levelHeight-

screenHeight;

levelX = aX;

levelY = aY;

}

Umieszczanie aplikacji na telefonieZa pomocą Bluetooth:

• Włączamy w telefonie w opcjach połączenia Bluetooth;• Znajdujemy na dysku wygenerowany plik Platformowka.jar (możemy go znaleźć w pod-

katalogu dist naszego projektu);• Zaznaczamy go i klikając prawy klawisz myszki, rozwijamy menu kontekstowe, z którego

wybieramy do Bluetooth. Rozwija się lista z urządzeniami, do których możemy przepro-wadzić transfer pliku;

• Jeśli nie ma na tej liście naszego telefonu, wybieramy Szukaj innych urządzeń;• Po udanym nawiązaniu połączenia PC-telefon komórkowy, na telefonie pojawi się pyta-

nie o zgodę na odebranie pliku, a po odebraniu trzeba zaakceptować chęć instalacji na-szej gry i wybrać miejsce docelowe Gry lub Aplikacje;

• Po udanej instalacji telefon zapyta, czy uruchomić grę.

Za pomocą sieci (ang. OTA, Over The Air):

• Umieszczamy oba pliki naszej gry Platformowka.jad oraz Platformowka.jar na dowolnym serwerze online;

• W telefonie, który musi mieć poprawnie skonfigurowane ustawienia sieciowe, urucha-miamy przeglądarkę internetową (z tym użytkownicy mają najwięcej problemów, aby dobrze skonfigurować telefon, zajrzyj na stronę operatora swojej sieci);

• W uruchomionej przeglądarce wpisujemy bezpośredni adres do pliku Platformowka.jad umieszczonego na serwerze, np.: http://www.mojadomena.pl/gry/Platformowka.jad;

• Po udanym połączeniu z serwerem pobrany zostanie plik *.jad, a z niego informacje o grze i jej rozmiarze. Jeśli zaakceptujemy chęć instalacji gry, rozpocznie się pobieranie pli-ku jar, a następnie instalacja;

• Po udanej instalacji telefon zapyta, czy uruchomić grę.

Rysunek 4. Widok warstwy maski w level edytorze – MappyWin32

Page 162: SDJ Extra 34 Biblia

162

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 163

la w celu weryfikacji, czy jest on kolizyj-ny (isTileCollision(tx, ty)). W dru-gim kroku badamy wektor prędkości: jeśli

f_velocityY > 0, to znaczy, że wektor jest skierowany w dół i należy sprawdzić, czy czasem nie nastąpiła kolizja z podłożem.

Kolizja taka wystąpi wtedy i tylko wtedy, gdy odległość między środkiem collision box'a i środkiem tila (dy) jest mniejsza od sumy połowy tila (d2) i połowy wysokości prostokąta kolizyjnego (d1), czyli w sumie, gdy spełniony jest warunek d1+d2<dy. Jeśli wystąpi taka kolizja, to ustawiamy zmien-ną onGround na wartość true oraz korygu-jemy współrzędną Y naszej postaci o war-tość głębokości kolizji. Pozostaje jeszcze obsługa animacji postaci. Reprezentację graficzną naszego bohatera możemy spró-bować zrobić samemu, korzystając z do-wolnego programu graficznego, lub poszu-kać darmowych animacji w sieci. Ja popro-siłem o pomoc znajomego grafika o pseu-donimie Nelson (Bartosz Willim, Nanoga-mes), który od wielu lat przygotowuje gra-fikę na potrzeby gier komputerowych. Bar-tek znalazł chwilkę czasu i przygotował mi potrzebne animacje. Jak dla mnie bomba! Przygotowana animacja ma 4 klatki dla stanu spoczynku, 6 klatek reprezentują-cych ruch, 1 klatkę dla skoku oraz 3 klatki przewidziane na sytuację, gdy nasz boha-ter ginie. Ilość klatek definiują stałe z pre-fiksem ANIM_FRAMES_ w klasie Character. Nie możemy zapomnieć o tym, że musimy obsługiwać animacje dla dwóch kierun-ków ruchu: w lewo oraz w prawo. Zmien-na characterSideLeft będzie określać ak-tualną orientację postaci. Dodatkowo trze-ba zdefiniować czas trwania jednej klatki ONE_FRAME_TIME (100 milisekund). Meto-da updateAnimation(timeframe), przed-stawiona na Listingu 12, odpowiada za odtwarzanie animacji. Animacja ruchu w lewo lub prawo jest zapętlona, a zmien-na animLoopCount jest licznikiem aktual-nie odtwarzanej animacji. Należy jeszcze zwrócić uwagę na pętlę while(), w meto-dzie updateAnimation(), która pełni tutaj rolę synchronizatora. W sytuacji gdy odpali-my naszą grę na wolniejszym telefonie, nie musimy redukować ilości klatek anima-cji, aby zachować całkowity czas jej trwa-nia. Nasz synchronizator będzie wyświe-tlał co drugą albo co trzecią klatkę, zależ-nie od czasu trwania ramki. Nasza anima-cja tym samym staje się niezależna od szyb-kości telefonu.

Dochodzimy do momentu, w którym nasz bohater sprawnie biega po planszy i wskakuje na platformy, a kiedy stoimy w miejscu, oddy-cha pełną piersią.

Dodajemy przeszkadzajki i wrogówCzas postawić na drodze naszego bohate-ra wrogów: w każdej grze musi być to coś, 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-

Listing 9. Metoda zmiany stanu postaci

public void setState(int state) {

if(state == characterState || die)

return;

switch(state) {

case STAND:

characterFrameMin = (characterSideLeft) ? resOffset : resOffset

+RES_ANIM_STAY_RIGHT_OFF;

characterFrameMax = characterFrameMin

+ANIM_FRAMES_STAND;

break;

case MOVE_LEFT:

characterFrameMin = resOffset

+RES_ANIM_RUN_LEFT_OFF;

characterFrameMax = characterFrameMin

+ANIM_FRAMES_RUN;

characterSideLeft = true;

animLoopCount = 0;

break;

case MOVE_RIGHT:

characterFrameMin = resOffset

+RES_ANIM_RUN_RIGHT_OFF;

characterFrameMax = characterFrameMin

+ANIM_FRAMES_RUN;

characterSideLeft = false;

break;

case JUMP:

characterFrameMin = (characterSideLeft) ?

resOffset+RES_ANIM_JUMP_LEFT_OFF :

resOffset+RES_ANIM_JUMP_RIGHT_OFF;

characterFrameMax = characterFrameMin

+ANIM_FRAMES_JUMP;

break;

case FIGHT:

characterFrameMin = (characterSideLeft) ?

resOffset+RES_ANIM_JUMP_LEFT_OFF:

resOffset+RES_ANIM_JUMP_RIGHT_OFF;

characterFrameMax = characterFrameMin

+ANIM_FRAMES_FIGHT;

break;

case HURT:

characterFrameMin = (characterSideLeft) ?

resOffset+RES_ANIM_HURT_LEFT_OFF :

resOffset+RES_ANIM_HURT_RIGHT_OFF;

characterFrameMax = characterFrameMin

+ANIM_FRAMES_HURT;

die = true;

break;

}

animLoopCount = 0;

characterFrame = characterFrameMin;

characterFrameTime = 0;

characterState = state;

}

Page 163: SDJ Extra 34 Biblia

162

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 163

nien mieć swoje charakterystyczne cechy, odróżniające go od pozostałych. Może być to odmienny sposób poruszania się, inny rodzaj broni, większa wytrzymałość albo przynajmniej odmienna kolorystyka czy ilość punktów przyznanych za zniszcze-nie. W naszej prostej grze proponuję zaim-plementować dwa typy przeciwników. Typ pierwszy będzie czekał aż hero pojawi się w zdefiniowanym promieniu widzenia. Kie-dy odległość między hero i przeciwnikiem będzie mniejsza lub równa zdefiniowanej, wróg natychmiast rozpocznie wędrówkę w kierunku bohatera. Drugi typ to klasyczny przeciwnik patrolujący obszar od znacz-nika do znacznika, utrudniając bohatero-wi swobodne poruszanie się po planszy. Kolizja wroga z bohaterem odbiera temu drugiemu energię. Wielkość traconej ener-gii zdefiniujemy stałą LOST_ENERGY wyra-żoną w procentach i umieszczoną w pli-ku Config.java. Patrząc z programistycz-nego punktu widzenia, każdy wróg bę-dzie obiektem opisanej wcześniej klasy Character. Jak dodać nowe postacie do na-szej gry? W tym celu wracamy do edytora poziomów i tworzymy drugą warstwę: bę-dziemy nazywać ją maską (patrz: Ramka Dodawanie warstwy w edytorze poziomów). Maska musi mieć dokładnie takie same pa-rametry jak mapa planszy: zarówno roz-miar, jak i wielkość kafelka. Rozmieścimy na niej pozycje naszych przeciwników oraz znaczniki, które będą wyznaczały grani-ce poruszania się wrogów. Do istniejącego tileset'u dodajemy kilka nowych tili, które będą symbolizowały odpowiednio:

• [E1]: przeciwnik typu pierwszego;• [E2]: przeciwnik drugiego typu;• [strzałka w lewo]: znacznik poruszania

się w lewo;• [strzałka w prawo]: znacznik poruszania

się w prawo.

Pamiętajmy, że wszystko, co umieszczamy na masce, nie będzie widoczne na ekranie. Teraz pozostaje tylko umieścić nowo utworzone ti-le maski w odpowiednich miejscach. Umie-ściłem dwóch wrogów na samym dole plan-szy oraz dwóch na platformach, po lewej i po prawej stronie poziomu. Eksport maski robimy w identyczny sposób jak w przypad-ku pierwszej warstwy. Wygenerowaną sta-tyczną tablicę bajtów maskLevel[] dodajemy do klasy LevelLayer i dopisujemy kilka me-tod do obsługi tej tablicy (patrz: Listing 13). Obsługę wrogów implementujemy w silniku gry, w klasie Game. Metody z przedrostkiem enemy będą realizowały to zadanie, ich zawar-tość przedstawiona jest na Listingu 14. Meto-da enemyInit() tworzy obiekty wrogów oraz nadaje im odpowiednie pozycje na planszy pobrane z warstwy maski; wywoływana jest

tylko raz na początku. Po utracie życia, kiedy trzeba przywrócić pozycje startowe wrogów, wywołujemy metodę enemyRestore(). Koli-zja z bohaterem sprawdzana jest w metodzie enemyCheckCollision(), zaś rysowanie wro-gów zaimplementowano w enemyDraw(). Me-chanizm sztucznej inteligencji oraz wszystkie obliczenia związane z obsługą wrogów odby-wają się co ramkę w metodzie enemyUpdate(). Dla typu pierwszego zaczynamy od spraw-dzenia odległości między naszym bohate-rem a obiektem enemy. Jeśli odległość będzie mniejsza od zdefiniowanej, ustawiamy stan poruszania się w lewo albo prawo, zależnie od położenia hero. Przeciwnik będzie nas go-nił tak długo, jak długo odległość między nim a bohaterem będzie mniejsza od zdefiniowa-

nej. Sprawdzanie wykonujemy tylko w osi po-ziomej. Zachowanie drugiego rodzaju prze-ciwnika nazywa się fachowo patrolowaniem obszaru typu A-B-A. Przeciwnik będzie prze-mieszczał się w danym kierunku do czasu napotkania znacznika, czyli maski z nowym kierunkiem. Porusza się od punktu A do punktu B i następnie wraca do punktu A. W ten sposób można wyznaczyć przeciwnikowi całkiem ciekawe ścieżki, obszary do patrolo-wania. Nasze znaczniki na masce to strzałki w lewo i prawo. Dla tego typu sprawdzamy, czy wystąpiła kolizja ze znacznikiem kierun-kowym, jeśli tak, to ustawiamy kierunek po-ruszania się hero zgodny z kierunkiem znacz-nika. Takie rozwiązanie daje duże możliwości manewru, np. możemy dodać znaczniki przy-

Listing 10. Obsługa poruszania się postaci

public void moveLeft() {

f_velocityX -=

((onGround ? speed : speed>>1)*timeframe)>>5;

setState(MOVE_LEFT);

}

public void moveRight() {

f_velocityX +=

((onGround ? speed : speed>>1)*timeframe)>>5;

setState(MOVE_RIGHT);

}

public void jump() {

if(!onGround)

return;

f_velocityY -= JUMP_POWER;

setState(JUMP);

}

Listing 11. Sprawdzanie kolizji z podłożem

public void updateCollision() {

onGround = false;

int px = f_characterX>>Config.FP;

int py = f_characterY>>Config.FP;

//bottom

int tx = px >> LevelLayer.TILE_SHIFT;

int ty = (py+characterHeight2) >> LevelLayer.TILE_SHIFT;

int dy = (ty<<LevelLayer.TILE_SHIFT) - py -

LevelLayer.TILE_SIZE;

//omijamy efekt wciągania w góre, kolizja do polowy tila

if( LevelLayer.isTileCollision(tx, ty) &&

f_velocityY>0 &&

dy > -(LevelLayer.TILE_SIZE>>1) )

{

py += dy;

f_characterY = py << Config.FP;

f_velocityY = 0;

onGround = true;

}

}

Page 164: SDJ Extra 34 Biblia

164

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 165

spieszania, zwalniania itd. Jeśli w chwili koli-zji tylko nasz bohater będzie znajdował się w stanie walki (FIGHT), życie utraci przeciwnik. W przypadku gdy obie postacie będą w stanie walki, wówczas oboje tracą energie. Możemy oczywiście dodać wiele innych, ciekawych za-chowań dla przeciwnika i wzbogacić jego in-teligencję w zależności od potrzeb, jednakże w naszej grze poprzestaniemy na bardzo pro-stych mechanizmach AI. Brakuje tylko gra-fiki dla przeciwników. Możemy oczywiście przygotować takową osobiście, poprosić zna-jomego grafika o pomoc lub poszukać darmo-wych animacji w sieci. Ja z pomocą IrfanView zmienię grafikę naszego bohatera, wykorzy-stując funkcję Negative. W tym celu każdą klatkę należy otworzyć w IrvanView, wybrać z menu głównego IMAGE, a następnie z roz-winiętego podmenu opcję NEGATIVE. Gra-fikę dla drugiego przeciwnika uzyskałem na podobnej zasadzie, przerabiając naszego hero: tym razem przy pomocy opcji IMAGE > Co-

nvwert. Jest to bardzo prosty sposób na wy-konanie potrzebnej grafiki, choć jakość pozo-stawia wiele do życzenia... Postanowiłem jed-nak skupić się przede wszystkim na funkcjo-nalności.

Dodajemy bonusy i dodatkiNasza gra wygląda już całkiem przyzwoicie. Można poruszać się po planszy, swobodnie wskakiwać na platformy, a także spotkać wro-gów. Jest to dobry moment na dołożenie bra-kujących elementów, które założyliśmy so-bie wstępnie, projektując naszą grę. Mowa tu o rozmieszczeniu monet, które nasz bo-hater będzie skrupulatnie zbierał. Trzeba też ukryć na planszy klucze, których znale-zienie będzie warunkiem koniecznym ukoń-czenia poziomu. Przyda się także dodanie bo-nusów, pozwalających uzupełnić energię, ze-brać dodatkowe życie lub zwiększenie liczni-ka, określającego ile czasu pozostało na ukoń-czenie planszy. Wracamy do edytora pozio-

mów, otwieramy naszą warstwę maski, na której rozmieszczaliśmy wrogów. Dodajemy do tileset'u kolejne kafelki maski, które będą symbolizować odpowiednio:

• [kluczyk]: kluczyk;• [klepsydra]: zwiększenie licznika czasu;• [serduszko]: dodatkowe życie;• [moneta]: punkty.

Elementy uzupełniające starannie rozmiesz-czamy na planszy. Kiedy uznamy, iż ich ilość jest wystarczająca, możemy eksporto-wać warstwę maski. Starą tablicę maski za-stępujemy nową, po czym możemy bezpo-średnio korzystać z niej w kodzie. Wykrywa-nie kolizji bohatera z monetami lub bonusa-mi realizuje instrukcja switch(), umieszczo-na w głównej pętli gry w klasie Game (patrz: Listing 15). W zależności od wykrytej koli-zji efekty widzimy na pasku statusu, w po-staci dodatkowego serduszka, punktów czy licznika znalezionych kluczy. Na tym etapie trzeba też ustalić czas potrzebny na ukoń-czenie planszy. Początek zwykle wiąże się z zapoznaniem się gracza z obsługą klawi-szy, poruszaniem po planszy i podstawowy-mi zasadami gry. Gracz powinien więc mieć wystarczająco dużo czasu. Zakładamy, że 180 sekund, czyli 3 minuty, wystarczy na to zadanie. Czas ten można wydłużyć o kolejne 30 sekund, zbierając klepsydrę. Za zebranie każdej monety gracz uzyskuje 100 punktów, a za kluczyk dodatkowo 1000 punktów. Wa-runkiem zakończenia poziomu jest oczywi-

Listing 12. Odtwarzanie animacji

public void updateAnimation(int timeframe) {

characterFrameTime += timeframe;

while(characterFrameTime > ONE_FRAME_TIME) {

characterFrame++;

if(characterFrame >= characterFrameMax) {

characterFrame = characterFrameMin;

animLoopCount++;

}

characterFrameTime -= ONE_FRAME_TIME;

}

}

Listing 13. Metody do obsługi maski

public static byte[][] getLevelMask() {

return maskLevel;

}

public int getMask(int x, int y) {

x = (x>>FP)>>TILE_SHIFT;

y = (y>>FP)>>TILE_SHIFT;

return maskLevel[y][x];

}

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

x = (x>>FP)>>TILE_SHIFT;

y = (y>>FP)>>TILE_SHIFT;

return (maskLevel[y][x]==type);

}

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

x = (x>>FP)>>TILE_SHIFT;

y = (y>>FP)>>TILE_SHIFT;

maskLevel[y][x] = (byte)type;

}

Rysunek 5. Screenshot z naszej gry

Page 165: SDJ Extra 34 Biblia

164

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 165

Listing 14. Metody do obsługi wrogów

private void enemyInit(int count) {

enemy = new Character[count];

count = 0;

for(int y=0; y<LevelLayer.maskLevel.length; y++)

for(int x=0; x<LevelLayer.maskLevel[y].length; x++)

{

if( LevelLayer.maskLevel[y][x] ==

LevelLayer.MASK_ENEMY_TYPE1 )

{

enemy[count] = new Character(

x<<LevelLayer.TILE_SHIFT,

y<<LevelLayer.TILE_SHIFT,

ENEMY_WIDTH,

ENEMY_HEIGHT,

Res.IMG_ENEMY1_STAY_LEFT,

LevelLayer.maskLevel[y][x]);

enemy[count].setSpeed(ENEMY_SPEED);

count++;

}

if( LevelLayer.maskLevel[y][x] ==

LevelLayer.MASK_ENEMY_TYPE2 )

{

enemy[count] = new Character(

x<<LevelLayer.TILE_SHIFT,

y<<LevelLayer.TILE_SHIFT,

ENEMY_WIDTH,

ENEMY_HEIGHT,

Res.IMG_ENEMY2_STAY_LEFT,

LevelLayer.maskLevel[y][x]);

enemy[count].setSpeed(ENEMY_SPEED);

count++;

}

}

}

private void enemyRestore(boolean forceLife) {

for(int y=0, count =0; y<LevelLayer.maskLevel.length; y++)

for(int x=0; x<LevelLayer.maskLevel[y].length; x++)

if( LevelLayer.maskLevel[y][x] ==

LevelLayer.MASK_ENEMY_TYPE1 ||

LevelLayer.maskLevel[y][x] ==

LevelLayer.MASK_ENEMY_TYPE2 )

{

if(forceLife)

enemy[count].setLife();

if(!enemy[count].isDied())

enemy[count].setPos(

x<<LevelLayer.TILE_SHIFT,

y<<LevelLayer.TILE_SHIFT);

count++;

}

}

private void enemyUpdata(int time) {

if(enemy == null)

return;

for(int i=0; i<enemy.length; i++)

if(enemy[i] != null) {

enemy[i].update(time);

switch(enemy[i].getType())

{

case LevelLayer.MASK_ENEMY_TYPE1:

int dx = (enemy[i].getX() -

hero.getX())>>FP;

if(Math.abs(dx) < ENEMY_DETECTION &&

Math.abs(dx) > 0)

{

if(dx > 0)

enemy[i].moveLeft();

else

enemy[i].moveRight();

}

enemyCheckCollision(i);

break;

case LevelLayer.MASK_ENEMY_TYPE2:

if(enemy[i].getSideLeft())

enemy[i].moveLeft();

else

enemy[i].moveRight();

if( layer.isMask(enemy[i].getX(),

enemy[i].getY(),

LevelLayer.MASK_RIGHT))

enemy[i].setSideLeft(false);

if(layer.isMask(enemy[i].getX(),

enemy[i].getY(),

LevelLayer.MASK_LEFT))

enemy[i].setSideLeft(true);

enemyCheckCollision(i);

break;

}

}

}

private void enemyCheckCollision(int i) {

if(!enemy[i].isDied() &&

!hero.isHurt() &&

hero.isCharacterCollision(

enemy[i].getX(), enemy[i].getY()))

{

heroEnergy -= LOST_ENERGY;

if(heroEnergy < 0) {

heroLife--;

hero.setDie();

if(heroLife != 0)

heroEnergy = 100;

}

if(hero.getState() == Character.FIGHT)

enemy[i].setDie();

}

}

private void enemyDraw(Graphics g) {

if(enemy == null)

return;

for(int i=0; i<enemy.length; i++)

if(enemy[i] != null)

enemy[i].draw(g);

}

Page 166: SDJ Extra 34 Biblia

166

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 167

ście zebranie wszystkich kluczy. Wszystkie te parametry definiujemy w pliku Config.ja-va. Wielkimi krokami zbliżamy się do koń-ca naszej przygody z programowaniem gry na komórkę.

Brakujące elementy i testyMusimy jeszcze raz wrócić na chwilę do edy-tora planszy i dodać znacznik pozycji starto-wej bohatera, dorobimy symbol zielonego okręgu i umieścimy na masce. Potrzebujemy jeszcze graficzny interfejs użytkownika (ang. GUI) w postaci paska statusu, na którym wy-świetlone zostaną punkty, czas oraz ilość ze-branych kluczy. W tym celu użyjemy grafi-ki z planszy. Ważne, aby wszystko było czy-telne dla gracza. Na potrzeby naszego projek-tu, do wyświetlania napisów i wartości uży-jemy czcionek systemowych. Czytelników, którzy chcieliby poeksperymentować, zachę-cam do implementacji własnej klasy obsługu-jącej czcionki bitmapowe. Systemowa meto-da drawString() na komórkach działa bar-dzo wolno, a co gorsza, na każdym telefo-nie czcionki mogą się znacząco różnić. Do-robimy jeszcze dwukolorowy pasek energii. Za pomocą boolowskiej zmiennej gameOver będziemy określać stan zakończenia gry. W momencie, w którym zabraknie czasu na ze-branie wszystkich kluczy bądź gdy stracimy wszystkie życia, zmienna ta przyjmie war-tość true. Kiedy zbierzemy wszystkie klu-cze, inna zmienna: levelCompleted, przyj-muje wartość true, zaś na ekranie pojawia się komunikat Level Completed. Sterowanie naszym bohaterem jest możliwe za pomo-cą d-pada/joysticka, jednak z uwagi na specy-fikę platformy niektórzy gracze używają kla-wiatury numerycznej – dlatego też gra obsłu-guje również klawisze 2, 4, 5, 6, oraz 8, jako alternatywną wersję kontrolera. Główna kla-sa gry Game dziedziczy po klasie Canvas, w której jest kilka metod do obsługi zdarzeń ta-kich jak:

• showNotify(), • hideNotify(), • pointerPressed(), • pointerDragged(), • pointerReleased(), • keyPressed(), • keyReleased().

Listing 15. Główna pętla gry

public void gameLogic(int time) {

if(gameOver || levelCompleted)

return;

heroTime -= time;

if(heroTime < 0) {

gameOver = true;

heroTime = 0;

}

if(direction>0 && gametime-pressTime > 10)

move(direction);

enemyUpdata(time);

if(hero != null) {

if(hero.isDied())

if(heroLife == 0)

gameOver = true;

else {

hero.setPos(startX, startY);

enemyRestore(false);

hero.setLife();

}

layer.setPos(hero.getX()>>FP, hero.getY()>>FP);

hero.update(time);

switch(layer.getMask(hero.getX(), hero.getY())) {

case LevelLayer.MASK_COIN:

heroScore += COIN_PTS;

layer.setMask(hero.getX(), hero.getY(),

LevelLayer.MASK_EMPTY);

break;

case LevelLayer.MASK_HEART:

heroLife++;

layer.setMask(hero.getX(), hero.getY(),

LevelLayer.MASK_EMPTY);

break;

case LevelLayer.MASK_KEY:

heroKeys++;

heroScore += KEY_PTS;

levelCompleted =

(heroKeys == keyMax) ? true : false;

layer.setMask(hero.getX(), hero.getY(),

LevelLayer.MASK_EMPTY);

break;

case LevelLayer.MASK_TIME:

heroTime += EXTRA_TIME*1000;

layer.setMask(hero.getX(), hero.getY(),

LevelLayer.MASK_EMPTY);

break;

}

}

}

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.

Page 167: SDJ Extra 34 Biblia

166

Programowanie JavaME

SDJ Extra 34 Biblia

Gra w Javie na komórkę

www.sdjournal.org 167

Uzupełniamy tylko ciało dwóch ostatnich metod (patrz: Listing 16). Nasza gra nada-je się do tego, aby wgrać ją na telefon i dać bratu lub siostrze do zabawy (patrz: ram-ka Wgrywanie aplikacji na telefon). Uwagi użytkowników na tym etapie produkcji gry są niezwykle cenne i pomagają dobierać pa-rametry silnika, tak aby nasza platformów-ka była jak najbardziej grywalna. Jako uko-ronowanie naszych wysiłków dodamy jesz-cze ikonkę gry, która będzie widoczna w me-nu telefonu. W tym celu wystarczy przygo-tować plik graficzny w formacie PNG i roz-miarze 24x24 pikseli, umieścić go w naszych zasobach (np. w katalogu hero) oraz nazwać go icon.png.

W tym momencie możemy uznać proto-typ naszej klasycznej platformówki za ukoń-czony!

PodsumowanieW powyższym artykule przedstawiłem bar-dzo podstawowy mechanizm powstawania gry w JME. Zaprezentowałem fundamen-talne aspekty i etapy takiego przedsięwzię-cia: dobieranie narzędzi, tworzenie fabuły, planowanie architektury, pisanie kodu. Na-sza gra nie posiada specjalnych efektów, któ-re przede wszystkim nadają ton grze. Skupi-łem się głównie na mechanice i funkcjonal-ności. Kolejnym etapem produkcji powinno być dopracowanie grywalności i wzbogace-nie o efekty.

Można przede wszystkim dorobić ani-macje zbieranych monet, kluczy. Punkty za zebrane monety powinny się pojawiać nad monetą. Przy starcie planszy można ją za-prezentować w całości, przewijając od koń-ca do punktu startu. Za zakończenie pozio-mu przed czasem naliczać dodatkowe punk-ty, oczywiście więcej wrogów z bardziej roz-winiętą inteligencją oraz wiele innych efek-tów i ulepszeń.

Listing 16. Obsługa klawiszy

protected synchronized void keyPressed(int keyCode) {

int key = 0;

try {

key = getGameAction(keyCode);

} catch (Exception e) {}

if( key == Canvas.LEFT ||

keyCode == KEY_NUM4 ) {

direction |= KEY_LEFT;

pressTime = gametime;

move(direction);

}

else

if( key == Canvas.RIGHT ||

keyCode == KEY_NUM6 ) {

direction |= KEY_RIGHT;

pressTime = gametime;

move(direction);

}

if( key == Canvas.UP ||

keyCode == KEY_NUM2 ) {

direction |= KEY_TOP;

pressTime = gametime;

move(direction);

}

if( key == Canvas.FIRE ||

keyCode == KEY_NUM5 )

hero.fight();

if( keyCode == KEY_RSK )

midlet.notifyDestroyed();

if( keyCode == KEY_LSK )

if(gameOver || levelCompleted) {

enemyRestore(true);

restoreGame();

}

}

protected synchronized void keyReleased(int keyCode) {

int key = 0;

try {

key = getGameAction(keyCode);

} catch (Exception e) {}

if( key == Canvas.UP ||

keyCode == KEY_NUM2 )

direction &= (0xFF^KEY_TOP);

else

if( key == Canvas.LEFT ||

keyCode == KEY_NUM4 )

direction &= (0xFF^KEY_LEFT);

else

if( key == Canvas.RIGHT ||

keyCode == KEY_NUM6 )

direction &= (0xFF^KEY_RIGHT);

}

CEZARIUSZ KLONKOWSKIPracuje na stanowisku Specjalista ds. Progra-mowania Gier Java w firmie Gamelion, wcho-dzącej w skład Grupy BLStream. Cezariusz ge-neralnie specjalizuje się w technologiach związanych z programowaniem gier, w szcze-gólności interesują go platformy mobilne. W branży pracuje około 6 lat z dorobkiem ponad 50 wydanych gier. Grupa BLStream powsta-ła, by efektywniej wykorzystywać potencjał dwóch szybko rozwijających się producentów oprogramowania – BLStream i Gamelion. Fir-my wchodzące w skład grupy specjalizują się w wytwarzaniu oprogramowania dla klientów korporacyjnych, w rozwiązaniach mobilnych oraz produkcji i testowaniu gier.Kontakt z autorem: [email protected]

Page 168: SDJ Extra 34 Biblia

168

Programowanie JavaME

SDJ Extra 34 Biblia

Testowanie aplikacji

www.sdjournal.org 169

Wstęp Rosnąca popularność telefonów komórkowych, jako skutecznej platformy aplikacyjnej, wią-że się nierozerwalnie z pojawieniem się oraz rozwojem specjalnej wersji Java dla tych urzą-dzeń. Jest ona znana pod nazwą J2ME lub Java ME (Java for Mobile Edition) i stanowi podzbiór funkcjonalności zdefiniowanych w Java 2 Plat-form, umożliwiając tworzenie aplikacji na sze-roki wachlarz terminali. W rzeczywistości pod-zbiór ten jest dodatkowo definiowany za pomo-cą określonych profili (CLDC/CDC/MIDP) i opcjonalnych wspieranych rozszerzeń, które to ostatecznie określają finalny dostępny interfejs API. Tak rozbudowana budowa platformy mo-bilnej jest konsekwencją różnorodności wspie-ranych urządzeń, na których to aplikacje, zwane midletami, powinny działać identycznie. Towa-rzysząca twórcom zasada: „programuj raz, uru-chamiaj wszędzie” jest oczywiście ograniczona granicami zdrowego rozsądku i wymaga uru-chomienia i przetestowania aplikacji na danym urządzeniu. Zazwyczaj aby osiągnąć sukces ko-mercyjny niezbędne jest wspieranie przez do-starczone aplikacje znaczącego procentu obec-nych na rynku terminali. Aby zobrazować skalę problemu, przytoczono dane z listopada 2008 r. za spółką GetJar, według których aby objąć 90

% modeli telefonów komórkowych używanych w Polsce, należałoby dostarczyć midlet wspie-rany przez 154 modele telefonów. Wyróżnić można atrybuty takie jak wielkość ekranu, do-stępna wielkość pamięci czy szybkość połącze-nia, które można z powodzeniem zweryfikować za pomocą symulatora programowego. Niestety pozostaje spora ilość zależności, które wynikają chociażby z innego stosu protokołów czy imple-mentacji maszyny wirtualnej, objawiające się w najbardziej niespodziewanych obszarach ko-du i tylko wyłącznie, gdy pracuje na rzeczywi-stym fizycznym urządzeniu. Aby zminimalizo-wać możliwość wystąpienia tego typu błędów, przed rozpowszechnieniem aplikacji dokonu-je się jego weryfikacji na docelowych urządze-niach. Przed twórcami aplikacji stoi zatem ko-nieczność ręcznego uruchomienia i przetesto-wania midletów na całkiem pokaźnej grupie telefonów. Praktyka testowania modelu refe-rencyjnego dla platformy jest skutecznym spo-sobem na zmniejszenie ich liczby. Przykłado-wo, według Java Verified Program, weryfikacja midletu na telefonie Motorola V600 jest rów-noznaczna z przetestowaniem modeli V500, V525 oraz V300. Można tego typu badanie zlecić zewnętrznej firmie, na przykład w ra-mach certyfikacji JVP, w rzeczywistości jed-nak rzadko kiedy z powodu sporych kosztów pokrywa się nimi nawet wiodące na rynku plat-formy, a co dopiero poszczególne modele telefo-nów. Dużo bardziej popularne jest wykorzysty-wanie własnej bazy rozproszonych testerów i te-stowanie wersji BETA midletu w ramach osta-

tecznej weryfikacji. To właśnie ta grupa odbior-ców, z racji oddalenia od zespołu i narzędzi pro-gramistycznych, jest głównym adresatem syste-mów zbierania danych debugowych, o których traktuje ta praca.

Zasady działaniai ograniczenia systemuBeta Tester zazwyczaj nie działa według za-projektowanego scenariusza, działając w spo-sób zbliżony do działania zwykłego użytkow-nika. Jeśli podczas obsługi aplikacji napotka na zachowanie, które według niego odbiega od oczekiwanego, ma możliwość zgłoszenia tego spostrzeżenia, wraz ze specyficznym lo-giem aplikacji. Log jest zapisem obserwacji działającej aplikacji. W najbardziej zaawanso-wanych rozwiązaniach zbiera dane z pozio-mu wywołań funkcji, zależnie od poziomu śledzenia aplikacji - wybranych lub wszyst-kich. Celem tego typu rozwiązań jest umoż-liwienie programistom prześledzenie i ewen-tualne odtworzenie scenariusza powodujące-go sytuację błędną w działaniu aplikacji. Jest to kluczowe zwłaszcza w przypadku analizy problemów wielowątkowych. W tym celu czę-sto nie wystarczają takie informacje jak kolej-ność otwieranych okien aplikacji, niezbędne jest odnotowanie zdarzeń w sensie programi-stycznym, ze szczególną uwagą zwróconą na ich kolejności. Tego typu rozwiązania charak-teryzują się pewnymi wspólnymi cechami nie-zależnymi od specyfiki obserwowanego syste-mu. Najważniejszą z nich jest brak interakcji z obserwowanym obiektem. Oczywiście jedynie rozwiązania wykorzystujące protokoły takie jak JTAG i zewnętrzne analizatory mogą za-gwarantować pełną hermetyzację środowiska pomiarowego od obserwowanego. Wszystkie systemy uruchamiane na tym samym fizycz-nym urządzeniu, w tym także rozwiązania tu przedstawiane, obciążone są pewną ilością ge-nerowanych zakłóceń wynikających z takich czynników jak:

Testowanie aplikacji na platformie J2ME

Dowiesz się:• jakie funkcję pełnia systemy zbierania da-

nych debugowych• jakie ograniczenia muszą spełniać takie sys-

temy w środowisku J2ME• w jaki sposób najbardziej optymalnie wyko-

rzystać dosępne zasoby

Poziom trudności

Zbieranie informacji debugowych w ograniczonym środowisku uruchomieniowym

Powinieneś wiedzieć• czym jest środowisko J2ME• czym są zagadnienia związane z testowaniem• jak pisać i uruchamiać aplikacje wielowątko-

we

Page 169: SDJ Extra 34 Biblia

168

Programowanie JavaME

SDJ Extra 34 Biblia

Testowanie aplikacji

www.sdjournal.org 169

• dodatkowe obciążenie CPU;• dodatkowe zużycie pamięci;• wykorzystanie innych zasobów systemo-

wych, takich jak semafory, uchwyty do plików itp.

W przypadku pamięci oraz procesora zakłóce-nia w pracy aplikacji łatwo staną się pomijalne pod warunkiem dysponowania rezerwą tych zasobów oraz odpowiednią optymalizacją ko-du obserwatorów. Niestety w przypadku pozo-stałych zasobów systemowych, zwłaszcza gdy realizują również funkcje synchronizacyjne, sytuacja jest dużo bardziej złożona. Generalnie zalecane jest, aby unikać lub przynajmniej mi-nimalizować wykorzystanie tych elementów w przypadku tego rodzaju systemów. W prze-ciwnym razie możliwe jest doprowadzenie do dwóch wersji interakcji patologicznych:

• problem przestaje występować w chwili włączenia instrumentów obserwacyjnych, na przykład z powodu zmiany kolejności wywołań spowodowanej czekaniem na se-mafor systemowy;

• problem występuje wyłącznie w przypad-ku włączenia instrumentów obserwacyj-nych, które powodują przykładowo w da-nym przypadku deadlock'a.

Należy pamiętać o tego typu właściwościach systemu, szczególnie jeśli w trakcie reproduk-cji zdarzenia pojawiają się problemy.

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

Kolejną niezwykle istotną cechą systemu jest gwarancja zachowania kolejności ewidencjono-wanych zdarzeń. Stanowi to dodatkowe wy-zwanie w świetle założeń przyjętych powyżej, a więc unikania klasycznych mechanizmów syn-chronizujących. Od architekta systemu zależy, którą z możliwych opcji zastosuje:

• wykorzystanie mechanizmu synchroniza-cyjnego – gwarantowana poprawna kolej-ność zachowania zdarzeń w logu kosztem potencjalnego wpływu na działanie syste-mu obserwowanego;

• ustalenie kolejności zdarzeń na podstawie wartości znacznika czasu lub pobranego indeksu bez synchronizacji – nieskutecz-na w przypadku wydziedziczeń pomiędzy wystąpieniem zdarzenia a pobraniem war-tości znacznika lub indeksu;

• zamknięcie zapisu w postaci atomowych transakcji z weryfikacją stanu po zakoń-czeniu – skutecznie potrafi wykryć kon-flikt dostępu, jednak nie w każdej sytuacji pomoże odnaleźć właściwą kolejność;

• rozdzielenie kontenerów rejestrujących zdarzenia pomiędzy poszczególnymi wąt-kami – brak konfliktu dostępu do zaso-bów okupiony jest ceną braku synchroni-zacji pomiędzy poszczególnymi zapisami zdarzeń.

Pomimo różnych zaprezentowanych podejść do fizycznego zbierania danych, ich zewnętrz-na struktura powinna pozostawać w większo-ści wypadków spójna. Określenie logicznej struktury logu zdarzeń sprowadza się do okre-ślenia atrybutów widocznych na liście zda-rzeń, gdzie kolejność wynika z ich chronologii.

Do najczęściej ewidencjonowanych atrybutów należą: znacznik czasu (timestamp), typ zdarze-nia (np. Event, Exception, Assert), aktywny wą-tek (priorytet, nazwa). Bardzo często dołącza się również dane specyficzne dla danego typu zdarzenia w różnej postaci (np. predefiniowa-nych ciągów znaków, zrzutów pamięci, drze-wa egzekucji itp). Zazwyczaj są to dane zbie-rane opcjonalnie na odpowiednim poziomie zbierania danych.

Ilość danych zbieranych przy rejestracji poje-dynczego zdarzenia ma istotny wpływ na suma-ryczną wielkość logu. Ilość dostępnej pamięci, przeznaczonej na przechowywanie danych de-bugowych, ograniczając wielkość logu, określa również przy założonej wielkości pojedyncze-go zgłoszenia maksymalny horyzont czasowy dla zbieranych danych. Najbardziej popularne implementacje wykorzystują mechanizm bufo-ra cyklicznego (cycle-buffer), który w przypadku wypełnienia całości dostępnego miejsca nadpi-suje najstarsze dodane dane. Poważnym manka-mentem tego podejścia jest możliwość nadpisa-nia danych istotnych z punktu widzenia obser-wowanej anomalii poprzez dane mniej ważne, będące wynikiem ciągłego logowania zdarzeń. Aby minimalizować wpływ tej właściwości na przydatność systemu, stosuje się odpowied-nio duże wielkości logów zdarzeń, co jednak w przypadku systemów wbudowanych i ograni-czonych stanowi rozwiązanie nieakceptowalne.

O ile opisywane powyżej zagadnienia doty-czą sposobu działania systemu, nie mniej istot-na jest integracja systemu zbierania danych de-bugowych na poziomie kodu aplikacji. Użycie takich konstrukcji językowych, które umożli-wiają łatwą i praktycznie przeźroczystą inte-grację z obserwowanym systemem, jest w wielu

Page 170: SDJ Extra 34 Biblia

170

Programowanie JavaME

SDJ Extra 34 Biblia

wypadkach nieosiągalne. Dobrym przykładem mogą być tu usługi zbierania danych w rozwią-zaniach J2EE oparte o komponent Log4j. Nie-stety, bazuje on między innymi na mechaniź-mie refleksji, który nie jest dostępny na plat-formie J2ME. W przypadku tej drugiej wyma-gane będzie, aby programista fizycznie umie-ścił wywołania do systemu w obrębie obser-wowanej aplikacji. Istotna jest możliwość para-metryzowania wywołań systemu – na przykład poprzez ustalenie poziomu zdarzeń lub defi-nicje ich klas. Zaproponowany interfejs powi-nien charakteryzować się również skondenso-waniem zapisu, poprzez użycie odpowiednich wzorców. Dzięki temu dodawanie wywołań sys-temu debugowego do tworzonego kodu może stać się czynnością prawie automatyczną. We-dług autora całość pojedynczej interakcji z syste-mem debugowym (na przykład zgłoszenie zda-rzenia na poziomie critical będące efektem wy-stąpienia wyjątku) nie powinna zajmować wię-cej niż pojedynczą linijkę kodu.

Zebrane dane staną się cennym wkładem w rozwój aplikacji jedynie w przypadku, gdy trafią do osób odpowiedzialnych za ich anali-zę. System dystrybucyjny danych debugowych oraz szerzej zbierania i reagowania na zgłoszenia anomalii jest osobnym zagadnieniem w stosun-ku do tematu tego opracowania. Pomimo tego warto jedynie zaznaczyć, że może być to obszar kluczowy dla sukcesu funkcjonowania opisy-wanego systemu. Przedstawiając zarys rozwią-zania, warto wspomnieć o takich aspektach jak:

• łatwość i ergonomia dostarczania danych – minimalna ingerencja w proces wysy-łania zdarzenia ze strony testera, intuicyj-ny interfejs umożliwiający bezpośrednio po wystąpieniu anomalii na przesłanie da-nych do centrum analizy;

• możliwość ewidencjonowania zgłoszeń da-nego testera, stanowiący czynnik motywu-jący do dalszego testowania aplikacji;

• istnienie zwrotnego kanału komunikacyj-nego, pozwalającego na doprecyzowanie zgłoszenia w przypadku problemów z jego reprodukcją.

Specyfika systemu wykorzystania pamięci dla ograniczonego środo-wiska uruchomieniowegoOgraniczenia platformy uruchomieniowej ma-ją istotny wpływ na wybór architektury oraz implementacji systemu. Obszary najbardziej kluczowych ograniczeń wymagają dodatkowe-go omówienia proponowanych rozwiązań. Na-leżą do nich:

• zajętość pamięci;• moc obliczeniowa;• redystrybucja/konfiguracja.

Zajętość pamięci jest w przypadku aplikacji zbierającej dane debugowe najbardziej kluczo-

wym ograniczeniem. Dodatkowo niezwykle istotnym ograniczeniem jest konieczność pre-alokowania pamięci dla bufora danych debu-gowych. Jest to podyktowane zarówno wzglę-dami wydajnościowymi (brak alokacji pamię-ci przy logowaniu zdarzenia), jak i bezpieczeń-stwa (system w sytuacjach krytycznych może nie mieć możliwości zaalokowania pamięci). Wpływ na wymaganą wielkość mają zarówno wielkość pojedynczego rekordu, jak i ich suma-ryczna ilość. Wybory, przed jakimi staje projek-tant systemu to zatem optymalizacja wyko-rzystania dostępnej pamięci poprzez taki do-bór zbieranych atrybutów oraz wyboru do za-pisu odpowiednich rekordów, które w konse-kwencji nieść będą najwięcej informacji o zda-rzeniach w okolicy incydentu. Pomocne przy rozwiązaniu tego problemu może być odwró-cenie pytania: Które dane powinniśmy zbierać?, stawiając w jego miejsce: Bez których danych mo-żemy sobie poradzić?. Zakładając wyeliminowa-nie redundancji danych na poziomie pojedyn-czego rekordu, pozostaje zatem wybranie z ca-łego zbioru rekordów tych, które wniosą naj-większą wartość w procesie analizy i repro-dukcji problemu. W tym celu wprowadzenie poziomu zdarzeń jest najbardziej naturalnym sposobem na rozwiązanie zagadnienia. Każ-dy rekord w logu charakteryzować się będzie wagą, czyli istotnością dla analizatora. Typo-we zdarzenia takie jak przejścia po domyślnym drzewie sterowania zazwyczaj otrzymywać bę-dą niski poziom istotności, w przeciwieństwie do wszystkich zdarzeń niespodziewanych. Na etapie implementacji bądź kolekcjonowania danych określa się poziom dokładności zbiera-nych logów, co implikuje liczbę gromadzonych danych. Aby wypełnić zobowiązanie co do za-jętości pamięci, wykorzystać można konstruk-cje bufora cyklicznego, nadpisując dane, które pozostają w logu najdłużej. Z punktu widze-nia analizy może się jednak okazać, że w wie-lu przypadkach albo liczba gromadzonych da-nych była tak duża (wysoka dokładność logo-wania), że w buforze nadpisane zostały istotne dane z początku występowania problemu, albo ilość danych jest zbyt mała (niska dokładność logowania), aby dobrze odczytać tło procesu, który miał miejsce. Kolejnym udogodnieniem może być stworzenie kilku równoległych bu-forów cyklicznych obsługiwanych na zasadzie cyklicznego kontenera. Pojawienie się w bufo-rze zdarzenia krytycznego powodowałoby, iż kolejne zdarzenia trafiałyby do następnego bu-fora. Dopiero gdy wszystkie bufory byłyby za-jęte, nastąpiłoby nadpisanie pierwszego z nich. W ten sposób zamiast jednego bufora o wielko-ści N rekordów, powstałyby M buforów, z któ-rych każdy pomieściłby N/M rekordów. Pomi-mo iż takie rozwiązanie wydaje się dużym udo-godnieniem w stosunku do klasycznego bu-fora cyklicznego, nie jest to jeszcze rozwiąza-nie optymalne. Natura obserwowanych zda-rzeń jest stochastyczna, dlatego może się zda-

rzyć, iż zdarzenia krytyczne powodujące prze-niesienie zapisu do kolejnego bufora wystąpią jeden po drugim. Jakkolwiek sytuacja taka nie jest rzadkością, to sposobem na optymalne wy-korzystanie dostępnej pamięci jest wykorzy-stanie mechanizmu safe-window. Polega on na umieszczeniu w przestrzeni kontenera danych okna, w obrębie którego zdarzenia zapisywane są w sposób analogiczny jak w klasycznym bu-forze cyklicznym. Pojawienie się zdarzenia kry-tycznego powoduje przeniesienie bezpieczne-go okna na kolejne rekordy w buforze danych. Jeśli bezpieczne okno zostało wykorzystane w całości, będzie to indeks większy o maksymal-ny rozmiar okna. W tym przypadku rozwiąza-nie safe-window będzie tożsame z kilkoma bu-forami cyklicznymi. Różnica ujawnia się w mo-mencie, gdy kolejne zdarzenia krytyczne wy-stąpią w odległości mniejszej niż maksymalny rozmiar okna. Spowoduje to utworzenie bez-piecznych okien o wielkości mniejszej niż mak-symalne, nie pozostawiając zaalokowanej i nie-wykorzystanej pamięci kontenera.

PodsumowanieProponowane rozwiązania wykorzystania pa-mięci zostały wykorzystane w rzeczywistych aplikacjach. W celu zbudowania komplekso-wego systemu zbierania danych debugowych niezbędne jest rozwiązanie pozostałych wspo-mnianych problemów, takich jak: dystrybucja danych, integracja z produktem czy minimali-zowanie wpływu na wydajność i wykorzystanie mechanizmów synchronizacyjnych oraz sku-teczna serializacja danych. Z powodu znacznej objętości podjętych tematów nie jest możliwe przedstawienie całości rozwiązań w tej pracy. Z całą pewnością ważne jest również wykorzysta-nie nowych możliwości, jakie pojawiają się wraz z rozwojem platformy J2ME, takich jak rozsze-rzenie JSR 190 – Event tracking API, które w znacznym stopniu może rozwiązać problem dystrybucji danych czy zewnętrznych bibliotek wspomagających logowanie. Niestety platforma mobilna nie doczekała się jeszcze systemów na tyle stabilncyh i uznanych jak Log4J na platfor-mie J2EE, stąd istnieje konieczność implemen-tacji takich rozwiązań samodzielnie. Jest to bo-wiem jedno z kluczowych narzędzi umożliwia-jących zapewnienie wsparcia midletu na wielu platformach moblinych bez względu na pro-ducenta czy model terminala, co jest obecnie głównym wymaganiem biznesowym stawia-nym przed tego typu aplikacjami.

MARCIN MACIEJEWSKIFascynat technologii mobilnych, realizujacy się od po-nad 7 lat w rozwoju aplikacji dedykowanych dla sys-temów komunikacyjnych (GSM, CDMA, TETRA) wy-korzystując różne platformy oraz technolgie (J2ME, ANDROID, Web Applications). Obecnie pracuje nad rozwojem aplikacji WEB/WAP dla systemów Tetra. Kontakt z autorem: [email protected]

Page 171: SDJ Extra 34 Biblia
Page 172: SDJ Extra 34 Biblia

172

Rozwiązania mobilne

SDJ Extra 34 Biblia

Lotus Notes Traveler

www.sdjournal.org 173

Wraz z rozwojem wszelkiej maści urządzeń przenośnych, jak te-lefony komórkowe, ipody itp.,

pojawiła się potrzeba korzystania z aplika-cji pracy grupowej również na tych urzą-dzeniach.

Początkowo była to głównie poczta, póź-niej kalendarz, a ostatnio coraz więcej apli-kacji dostępnych w tradycyjnym środowi-sku typu klient- serwer może być urucha-miana nie tylko w przeglądarce na kompu-terze stacjonarnym, ale również na drob-nych urządzeniach przenośnych. Do ich graficznej i logicznej prezentacji wykorzy-stuje się najczęściej wbudowaną mobilną przeglądarkę, co nie jest zawsze wygodne, wydajne oraz bezpieczne. Także od stro-ny deweloperskiej przygotowanie aplika-cji pod obie wersje – przeglądarki stacjo-narnej, korzystającej zazwyczaj z szybsze-go i tańszego łącza – oraz mobilnej na urzą-dzeniu przenośnym- nie jest zadaniem pro-stym.

Dodatkowe wymogi bezpieczeństwa nie ułatwiają tego procesu. Stąd też pojawiają się programy, które uruchamiane na urzą-dzeniu przenośnym są dedykowanym do wybranych usług rozwiązaniem, zapewnia-

jącym jednocześnie bezpieczną komunika-cję, autoryzację, kompresję danych itd.

Począwszy od wersji Lotus Notes Do-mino 8.0.1, dostępnej w Polsce od po-nad roku, użytkownicy tego oprogramo-wania wraz z zakupem licencji użytkow-nika klienta Lotus Notes nabywają pra-wo do korzystania z aplikacji Lotus No-tes Traveler, służącej do obsługi urządzeń mobilnych. Najnowsza wersja oprogramo-wania – 8.5, dostępna od stycznia bieżą-cego roku, umożliwia współpracę z syste-mem operacyjnym Windows Mobile 5 i 6 oraz Symbian S60 (3rd edition oraz featu-re pack 1 i 2).

Wystarcza to w zupełności do zaspoko-jenia potrzeb wynikających z posiadania przez firmę telefonów komórkowych, ipo-dów itp. urządzeń większości popularnych producentów oraz modeli.

Aplikacja Lotus Notes Traveler wyko-rzystuje do synchronizacji pomiędzy ser-werem a klientem sieć GSM lub GPRS (General Packet Radio Service) oraz WiFi przy użyciu standardu 802.11x. Synchro-nizacja danych odbywa się w obie stro-ny, tak więc wszelkie zmiany w kalenda-rzu czy skrzynce nadawczej w urządze-niu przenośnym mogą być przeniesione do skrzynki pocztowej i kalendarza na ser-werze pocztowym. Powiadamianie o no-wej wiadomości odbywa się na zasadzie push, czyli urządzenie mobilne powiada-mia właściciela o nowej wiadomości pocz-

towej na serwerze bez konieczności wy-konania manualnej synchronizacji (po-przez dzwonek, wibracje itp.– w zależno-ści od indywidualnych ustawień użytkow-nika). Oprócz wspomnianych usług, Tra-veler zapewnia obustronną synchroniza-cję z czynnościami do wykonania (to-do), centralną książką adresową oraz dzienni-kiem. Jeśli w danej chwili sieć jest niedo-stępna, wiadomość lub wpis do kalenda-rza zostaną zapisane na urządzeniu prze-nośnym oraz przesłane do serwera Domi-no przy najbliższej okazji wykrycia połą-czenia sieciowego.

Aplikacja działa jako zadanie na serwe-rze Lotus Domino, użytkownik pobiera ze strony serwera pakiet instalacyjny klienta, który po uruchomieniu i podaniu danych dostępowych umożliwia od razu korzysta-nie z oprogramowania. Administrator mo-że kontrolować działanie poprzez zarzą-dzanie dedykowanymi politykami na ser-werze oraz bezpośrednie wydawanie ko-mend z konsoli serwera.

Aby zwiększyć poziom bezpieczeństwa, można skonfigurować połączenie szyfro-wane przy użyciu protokołu SSL pomię-dzy urządzeniem mobilnym a serwerem. Możliwe jest również wykorzystanie posia-danego oprogramowania VPN (virtual pri-vate network). Traveler współpracuje prak-tycznie z każdym rodzajem VPNu oraz po-siada wbudowaną integrację z Lotus Mobi-le Connect, która umożliwia samodzielne zestawienie prywatnej sieci wirtualnej w modelu klient- serwer oraz serwer- usłu-ga WWW (szczególnie przydatne przy lo-gowaniu do usług np. w kafejce interne-towej).

Użytkowanie oraz obsługa aplikacji jest bardzo łatwa. Aplikacja posiada intuicyj-ny oraz funkcjonalny wygląd umożliwiają-cy szybką i prostą współpracę z oprogramo-waniem bez wprowadzania rewolucyjnych

Lotus Notes Traveler

W tym roku, zgodnie z danymi IBM, po raz pierwszy w historii, więcej ludzi na świecie będzie miało telefon komórkowy niż stacjonarny. Wiele produktów IBM jest dostępnych na platformach mobilnych. Ostatnim ważnym wydarzeniem w tym zakresie było wprowadzenie oprogramowania Lotus Notes Domino, czyli pakietu IBM do pracy grupowej na iPhone.

Dowiesz się:• Jak włączyć obsługę podstawowych aplika-

cji Lotus w urządzeniach mobillnych;• Jak zainstalować i skonfigurować Lotus No-

tes Travelera;• Jak uruchomić i zarządzać usługą na serwe-

rze i kliencie.

Powinieneś wiedzieć:• Jak zarządzać serwerem Lotus Domino (pod-

stawy);• Jak wykorzystywać HTML (podstawy).

Poziom trudności

Page 173: SDJ Extra 34 Biblia

172

Rozwiązania mobilne

SDJ Extra 34 Biblia

Lotus Notes Traveler

www.sdjournal.org 173

zmian w urządzeniu użytkownika oraz je-go dotychczasowym menu obsługi.

InstalacjaUruchomienie usługi Lotus Notes Traveler w istniejącym środowisku jest bardzo pro-ste i można je wykonać w ciągu jednej go-dziny.

Jeśli z aplikacji ma korzystać do kilku-dziesięciu osób jednocześnie, można prze-prowadzić ją na macierzystym serwerze Lo-tus Domino (oczywiście pod warunkiem, że nie jest on do tej pory obciążany w 100% lub blisko tej wartości). W przypadku przewidy-wanej większej liczby potencjalnych jedno-czesnych użytkowników narzędzia lub ze względów bezpieczeństwa, warto jest wy-dzielić odrębny serwer Lotus Domino tyl-ko pod Travelera. Jest to często o wiele wy-godniejsze, można skorzystać z wirtualiza-cji, aby w razie potrzeby dokładać kolejne procesory czy pamięć, umieścić maszynę w DMZ, otworzyć niezbędne porty tylko na serwerze pośredniczącym, a w razie awarii lub eksperymentów z użytkownikami mo-bilnymi nie ryzykuje się zagrożeniem cią-głości pracy aplikacji krytycznych, do któ-rych należy m.in. poczta elektroniczna pra-cowników firmy.

Instalacja na odrębnej maszynie pozwala również na uruchomienie usługi w przed-siębiorstwach, które nadal wykorzystu-ją starsze wersje serwerów Lotus Domi-no oraz klientów Lotus Notes. Najnowsza wersja aplikacji Lotus Notes Traveler wy-

maga serwera Domino 8.5.x, jednak obsłu-guje skrzynki pocztowe na serwerach Lo-tus Domino 7.0.2 lub nowszych. Możliwe jest więc dostawienie serwera Domino 8.5 w istniejącej domenie, nawet jeśli główny serwer jest w wersji niższej niż ósma. Do-datkowo, jeśli pracownicy korzystają ze starszej wersji klienta Lotus Notes, szablo-ny ich skrzynek będą wspierane- nie mogą być starsze niż wersja 6.5 (6.5 jest wspiera-na) i muszą znajdować się na serwerze nie starszym niż 7.0.2.

W chwili obecnej Lotus Notes Traveler współpracuje z następującymi systemami operacyjnymi serwerów:

• MS Windows 2003 Server Standard Edition 32 i 64 bity;

• MS Windows 2003 Server Enterprise Edition 32 i 64 bity;

• MS Windows 2003 Server R2 Stan-dard Edition 32 i 64 bity;

• MS Windows 2003 Server R2 Enter-prise Edition 32 i 64 bity.

Serwer Domino, do którego będzie się od-woływał Traveler, może być zainstalowa-ny pod innym systemem operacyjnym, np. AIX czy Linux.

Podczas komunikacji urządzenia prze-nośnego z serwerem wykorzystywane są trzy porty (Rysunek 3), które należy udo-stępnić aplikacji Travelera na zaporach po-między urządzeniami. Jeśli połączenie nie będzie używało SSLa, wówczas wystarczą

dwa porty – HTTP (80) oraz do synchro-nizacji danych (8642).

Jeśli aplikacja będzie instalowana na nowym serwerze Domino, należy spraw-dzić, czy ma on dostęp do skrzynek pocz-towych użytkowników. Najlepiej jest wyge-nerować wcześniej plik ID nowego serwe-ra na serwerze głównym domeny Domino, a następnie podczas konfiguracji instalato-ra serwera wybrać opcję dodatkowego ser-wera Domino (additional server) oraz po-dać ścieżkę do wygenerowanego wcześniej pliku ID serwera.

W ten sposób zaraz po uruchomieniu nowej maszyny znajdzie ona główny ka-talog użytkowników (musi mieć dostęp w sieci do serwera, na którym się znajdu-je centralna książka adresowa names.nsf) i pobierze z niego informacje o miejscu lo-kalizacji skrzynek pocztowych użytkow-ników.

Aby się upewnić, iż konfiguracja nowe-go serwera przebiegła pomyślnie, można np. sprawdzić w programie Domino Ad-ministrator, czy w książce adresowej no-wego serwera (names.nsf) znajdują się da-ne o wszystkich użytkownikach zarejestro-wanych w domenie. Jeśli jest pusta, należy sprawdzić, czy maszyna, na której jest no-wy serwer, może nawiązać połączenie sie-ciowe z głównym serwerem, oraz czy w wi-doku organizacji na liście znajduje się no-wy serwer.

Można też wydać w konsoli nowego ser-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 telefony Nokii (Symbian S60)

Rysunek 2. Widok skrzynki pocztowej w urządzeniu przenośnym

Page 174: SDJ Extra 34 Biblia

174

Rozwiązania mobilne

SDJ Extra 34 Biblia

Lotus Notes Traveler

www.sdjournal.org 175

prawność połączenia sieciowego i najszyb-ciej będzie odinstalować serwer Domino przeznaczony dla Travelera, usunąć kata-log Lotus/Data na dysku i zainstalować ser-wer jeszcze raz (trwa to kilka minut). Pra-widłowo zainstalowany serwer pobierze po uruchomieniu dane z centralnej książki ad-resowej i w zasadzie nie wymaga już więcej żadnych prac konfiguracyjnych.

Jeśli nowy serwer Lotus Domino został poprawnie zainstalowany i uruchomiony, można go zatrzymać (polecenie quit lub po prostu q w konsoli serwera) i rozpocząć in-stalację Travelera.

Po uruchomieniu programu instalacyj-nego, użytkownik jest proszony o wybra-nie wersji językowej instalatora, zanim przejdzie do kolejnych etapów konfigura-cji (Rysunek 4).

Po kliknięciu przycisku OK pojawi się ekran powitalny oraz przycisk uruchamia-jący centrum informacji o produkcie. Są w nim najnowsze dane o programie, opis in-stalacji, konfiguracji, rozwiązania najczę-ściej występujących problemów itp. Po przy-ciśnięciu Next pojawi się tekst umowy licen-cyjnej- aby przejść dalej, należy ją zaakcep-tować.

W kolejnym oknie pojawią się trzy opcje sposobu instalacji aplikacji (Rysunek 5). Wersja Complete zainstaluje wszystkie komponenty aplikacji na wskazanym ser-werze Lotus Domino, czyli binaria apli-kacji oraz stronę WWW programu, z któ-rej użytkownicy będą mogli pobrać aplika-cję kliencką do urządzenia przenośnego. Wersja Server Only instaluje tylko binaria

Tabela 1. Ustawienia autoinstalacji Travelera pod Windows Mobile

Parametr Wartość domyślna Opis

devPuserid brak Nazwa użytkownika Lotus Notes Traveler. Jeśli w adresach internetowych występuje standardowa nazwa, można ją tu umieścić, aby nie podawać pełnej nazwy przy konfiguracji oraz uprościć wpisywanie pełnej nazwy wszystkim użytkownikom, np. @ibm.com

devPprimary brak Nazwa serwera Domino z Travelerem, ew. jego numer IP

devPSSL 0 Włączanie/wyłączanie SSL: 0- wyłączone; 1- włączone

devPsyncP 80 Numer portu wykorzystywanego do synchronizacji HTTP

devPsyncPS 443 Numer portu wykorzystywanego do synchronizacji poprzez SSL

devPservR /servlet/traveler

Ścieżka dostępu do serwletu Lotus Notes Traveler, po zmianie należy prze-nieść pliki serwletu w odpowiednie miejsce na dysku serwera

devPport 8642 Numer portu do kanału PUSH (AutoSync)

devPvpnenable 1 Włączanie/wyłączanie Lotus Mobile Connect (LMC): 0- wyłączone; 1- włą-czone

devPvpnserver brak Nazwa hosta z bramką dostępową LMC, ew. jego numer IP

devPvpnport 8889 Numer portu do bramki LMC

devPvpnusesame 1 LMC ma używać tego samego loginu i hasła co Traveler: 0- nie; 1- tak

devPvpnuserid brak Login do LMC ma być inny od loginu do Travelera

devPvpnsavepassword 1 Czy hasło do LMC ma być zapisywane: 0- nie; 1- tak

devPvpnusedefault 1 Czy ma być używany domyślny profil LMC: 0- nie; 1- tak

devPvpnconnid brak Jeśli nie jest używany profil domyślny LMC, podaje nazwę używanego profilu

Rysunek 3. Konfiguracja portów pomiędzy urządzeniem przenośnym a Travelerem

Rysunek 4. Rozpoczęcie instalacji Lotus Notes Traveler 8.5.0.1

Page 175: SDJ Extra 34 Biblia

174

Rozwiązania mobilne

SDJ Extra 34 Biblia

Lotus Notes Traveler

www.sdjournal.org 175

aplikacji. Jest to przydatne w przypadku, kiedy stosowane są wysokie wymogi bez-pieczeństwa lub Traveler ma znajdować się na serwerze głównym razem, na któ-rym są skrzynki pocztowe, natomiast stro-na do pobierania oprogramowania klienc-kiego jest w DMZ lub będzie dostępna tyl-ko czasowo.

Wówczas należy dwukrotnie instalować Travelera, przy czym na serwerze obsłu-gującym tylko stronę WWW aplikacji, w trakcie instalacji należy wybrać ostatnią 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.

W tym celu należy w przeglądarce wpi-sać adres internetowy serwera Domino, któ-ry hostuje aplikację, dodając po niej nastę-pujący ciąg znaków: http://nazwa_hosta_Lo-tus_Domino/traveler/index.html.

Powinna pojawić się standardowa strona startowa Travelera, zawierająca odnośniki do pobrania oprogramowania klienckiego, tak jak na Rysunku 7. Nie jest ona rozbu-dowana, gdyż jej zadaniem jest dystrybu-cja oprogramowania na urządzenia mobil-ne. Można ją oczywiście rozbudować, do-

dając treści, grafikę itp. Jest to kwestia we-wnętrznej polityki i potrzeb firmy.

Konfiguracja i zarządzaniePo uruchomieniu serwera Lotus Domino oraz Travelera, sprawdzeniu dostępności pakietów klienckich w Internecie, należy jeszcze otworzyć wymagane porty na za-porach, aby umożliwić komunikację po-między urządzeniami a serwisem (Rysu-nek 3). Jeżeli ma być aktywne połączenie przy wykorzystaniu protokołu SSL, nale-

Tabela 2 . Ustawienia autoinstalacji Travelera pod Symbianem

Parametr Wartość domyślna Opis

userid brak Nazwa użytkownika Lotus Notes Traveler. Jeśli w adresach internetowych wystę-puje standardowa nazwa, można ją tu umieścić, aby nie podawać pełnej nazwy przy konfiguracji oraz uprościć wpisywanie pełnej nazwy wszystkim użytkowni-kom, np. @ibm.com

syncml.server brak Nazwa serwera Domino z Travelerem, ew. jego numer IP

syncml.protocol=http http/https Włączanie/wyłączanie SSL: http- wyłączone; https- włączone

syncml.port 80 Numer portu wykorzystywanego do synchronizacji HTTP

syncml.https.port 443 Numer portu wykorzystywanego do synchronizacji poprzez SSL

server.path /server/traveler

Ścieżka dostępu do serwletu Lotus Notes Traveler, po zmianie należy przenieść pli-ki serwletu w odpowiednie miejsce na dysku serwera

push.port 8642 Numer portu do kanału PUSH (AutoSync)

push.enabled 1 Włączanie/wyłączanie PUSH: 0- wyłączone; 1- włączone

predefined.connection brak Predefiniowanie wspólnego punktu dostępowego połączeń dla wszystkich użyt-kowników (Access point)

lmc.server brak Jeśli predefiniowanym punktem dostępowym = Mobility Client, wówczas należy podać nazwę hosta serwera LMC

lmc.port brak J.w. – numer portu LMC

lmc.iap brak J.w. – określenie połączenia LMC z najwyższym priorytetem

Rysunek 5. Wybór scenariusza instalacji Travelera

Page 176: SDJ Extra 34 Biblia

176

Rozwiązania mobilne

SDJ Extra 34 Biblia

ży wcześniej odpowiednio skonfigurować serwer Domino. Po wykonaniu i spraw-dzeniu wszystkich tych wymagań, moż-na uruchomić przeglądarkę w urządze-niu przenośnym, wpisać w niej adres in-ternetowy, taki sam jak przy sprawdza-niu poprawności działania Travelera, na-stępnie pobrać odpowiedni pakiet insta-lacyjny i uruchomić kreator instalacji. W jej trakcie użytkownik zostanie zapytany o podanie kilku informacji, jak m.in. ad-res hosta usługi, nazwę użytkownika, ha-sło itp. Aby maksymalnie uprościć cały proces, tak aby każdy korzystający z serwi-su nie zastanawiał się, jakiej odpowiedzi udzielić, można umieścić dodatkowe in-formacje na stronie startowej serwisu lub

zautomatyzować proces instalacji, edytu-jąc pakiet na serwerze i dodając do niego gotowe odpowiedzi, tak aby ograniczyć do minimum odpytywanie użytkownika. Aby rozbudować lub zmienić zawartość stro-ny startowej Travelera, wystarczy poddać edycji zawartość katalogu data/domino/html/traveler. Znajduje się on w głównym katalogu instalacyjnym serwera Domino. Dobrą praktyką jest wcześniej przygoto-wać zmienioną stronę, umieścić ją w in-nym miejscu na dysku serwera, a następ-nie we wskazanym katalogu umieścić tyl-ko odnośnik do niej. Nie należy również kasować żadnych plików z folderu Trave-lera, ponieważ niektóre z nich są używane przez serwlet aplikacji i usunięcie ich mo-

że być brzemienne w skutkach. Umiesz-czenie „obok” zmienionej strony ułatwia późniejsze zmiany nawet na żywym orga-niźmie oraz ustrzeże przed różnymi nie-spodziankami wynikającymi z błędów na nowej stronie, literówek itp. W ten spo-sób można m.in. szybko dystrybuować na urządzenia mobilne także inne aplikacje, np. klienta VPN- wystarczy dodać odno-śnik do pliku instalacyjnego, aby użytkow-nicy mogli go pobrać.

Z kolei aby wpisać do kreatora instalacji własne dane, ułatwiające użytkownikom instalację na urządzeniu mobilnym, nale-ży poddać edycji plik Bootstrap.nts. Znaj-duje się on w tym samym folderze co stro-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. 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.

Rysunek 7. Strona startowa Travelera, z której urządzenia przenośne pobierają oprogramowanie klienckie

Rysunek 6. Komunikat z konsoli serwera Domino potwierdzający uruchomienie aplikacji Travelera

ANDRZEJ OLSZTYŃSKIAndrzej Olsztyński - w IBM od pięciu lat zajmuje się narzędziami do pracy grupowej oraz zarzą-dzania obiegiem dokumentów, starszy specjali-sta oprogramowania Lotus, poza pracą - trener szachów

Page 177: SDJ Extra 34 Biblia
Page 178: SDJ Extra 34 Biblia

178

Rozwiązania mobilne

SDJ Extra 34 Biblia

Stary software – nowa platforma

www.sdjournal.org 179

Rynek urządzeń mobilnych co chwi-lę jest napędzany nowymi produk-tami różnych firm we wszystkich

jego segmentach. Wystarczy parę miesięcy, żeby trudności z zakupem najbardziej wy-szukanego gadżetu elektronicznego zamie-nić na trudności z jego odsprzedażą. Niewie-le jest na świecie rzeczy, które tracą tak szyb-ko na wartości jak sprzęt elektroniczny. Urzą-dzenia mobilne nie są tutaj wyjątkiem, każ-dy szanujący się producent w momencie pre-miery swojego najnowszego dziecka już pro-wadzi zaawansowane prace nad kolejnym je-go wcieleniem. Każde kolejne wcielenie to szansa na jeszcze większe zyski, a większe zy-ski napędzają kolejne wcielenia. Zakres takiej zmiany może być bardzo różny: od wymia-ny wyświetlacza LCD czy zmiany liczby lub układu klawiszy, po dodanie nowych modu-łów rozszerzających istniejący sprzęt, jak od-biornik GPS czy Bluetooth. Może wreszcie zaistnieć potrzeba pełnego przeprojektowa-nia platformy sprzętowej tak, by stała się pod-stawą dla implementacji funkcjonalności nie-możliwych do wprowadzenia na istniejącym urządzeniu. Bardzo rzadko jednak taką zmia-nę można ograniczyć do wyłącznie sprzęto-wej, niemal zawsze konieczne są modyfikacje w oprogramowaniu.

W każdym z tych przypadków pożą-danym scenariuszem jest osiągnięcie peł-nej integracji oprogramowania z nowym sprzętem przy ograniczeniu do minimum rozwspólnienia kodu źródłowego. Musi-my wszak pamiętać, że stare platformy czę-sto nie kończą życia wraz z wprowadze-niem ich nowszych odpowiedników i w długiej perspektywie wysiłek związany z utrzymaniem naszego software’u będzie tym większy, im więcej będzie w nim ele-mentów specyficznych dla poszczególnych platform. Z drugiej strony stary kod nie po-zwala w pełni skorzystać z nowych możli-wości, a tworzenie wielopoziomowej kom-pilacji warunkowej (#ifdef) dla kolejnych produktów wcale nie ułatwia późniejsze-go utrzymania takiego kodu. Srebrną kulę każdy niestety musi znaleźć sam.

Wybór mikroprocesoraNie będzie niespodzianką, jeśli powiemy, że ten wybór odgrywa znaczącą rolę w dalszym procesie decyzyjnym. Producen-ci mikroprocesorów prześcigają się w roz-wiązaniach ułatwiających tworzenie no-wych platform sprzętowych. Stos USB, IP, RS232 będące częścią uP przestają już dzi-wić, a czytając specyfikację najnowszych ARM-ów, można zacząć się zastanawiać, czy aby to, co chcemy na nim stworzyć, już przypadkiem nie jest zaimplemento-wane. Jednym z powodów dużej popular-ności procesorów z rodziny ARM w urzą-dzeniach mobilnych jest ich ograniczony

apetyt na prąd, który zawsze będzie defi-cytowym zasobem w tego rodzaju rozwią-zaniach. Jest jednak kilka innych elemen-tów, które na pierwszy rzut oka nie wydają się kluczowe, jednak są na tyle ważne, aby wziąć je pod uwagę.

Endianness (Rysunek 1). Wiele współ-czesnych procesorów oferuje możliwość zmiany ustawienia tego parametru. Zmia-na endiannessu w 600 tysiącach linii ko-du jest zadaniem nietrywialnym. Nie ma na rynku narzędzia, które byłoby w sta-nie wykonać tę czynność całkowicie auto-matycznie. Istnieją aplikacje wspomagają-ce, ale żadna nie da nam gwarancji rozwią-zania wszystkich konfliktów z tym związa-nych. Jak poradzić sobie w takiej sytuacji? Pomocne okazują się unit testy, czyli ze-staw parametrów wejściowych i oczekiwa-nej odpowiedzi, która dla poszczególnych jednostek testowanych powinna być iden-tyczna. Niekoniecznie musimy testować każdą funkcję, możemy to zrobić na całym module czy grupie funkcji. Jeśli nie mamy pewności, czy wszystkie możliwe miejsca w kodzie zostały przetestowane, możemy skorzystać z narzędzi do badania pokrycia kodu przez testy. Tak czy inaczej, unit te-sty zwiększają znacznie poziom ufności ta-kiej zmiany.

Podczas przeszukiwania w kodzie źródło-wym obszarów narażonych na problemy en-diannessu nie możemy zapomnieć o miej-scach rzutowania jednych struktur na dru-gie. Często do opisania interfejsu danego ta-ska używa się unii wszystkich możliwych struktur. Jeśli korzystamy z takiej definicji, to upewnijmy się, że ta właśnie definicja jest wykorzystywana po stronie nadawcy komu-nikatu. Przeszukajmy wszystkie unie w ko-dzie i sprawdźmy, czy rzutujemy ich pola po-między sobą.

Aby w przyszłości ustrzec się przed tego typu problemami, bardzo dobrym rozwią-

Stary software – nowa platformaW 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.

Dowiesz się:• jakie są kluczowe aspekty przenoszenia

oprogramowania urządzeń mobilnych na nowe platformy sprzętowe;

• jak wygląda taka migracja na przykładzie ra-diotelefonów systemu TETRA.

Poziom trudności

Powinieneś wiedzieć:• na czym polega specyfika programowania

urządzeń mobilnych;• czym jest TETRA i jakie są podstawowe za-

stosowania systemów łączności tego typu.

Page 179: SDJ Extra 34 Biblia

178

Rozwiązania mobilne

SDJ Extra 34 Biblia

Stary software – nowa platforma

www.sdjournal.org 179

zaniem jest oczywiście pisanie kodu nieza-leżnego od kolejności bajtów w słowie (ale o tym przekonamy się dopiero, jak będziemy zmieniali kod zależny). Z tego też powodu, jeśli kiedykolwiek przyjdzie nam zmieniać endianness istniejącej implementacji, to za-miast zamiany little na big lepiej zmieniać lit-tle lub big na niezależny.

Kolejnym rozwiązaniem ułatwiającym jest serializacja komunikatów przesyłanych pomiędzy taskami. Podczas ewentualnej zmiany tylko metody do (de)serializacji po-trzebują ingerencji. Dodatkowo będziemy mieli gotowy mechanizm, który można wy-korzystać, jeśli kiedykolwiek rozproszymy naszą implementację na kilka procesorów. Metody do deserializacji są również dosko-nałymi miejscami do sprawdzenia popraw-ności wartości parametrów w przesyłanym komunikacie.

System operacyjnyWybór z pozoru tylko jest bardzo prosty. Jednym z ważnych kryteriów jest oczywi-ście cena RTOS-a, ale wcale nie znaczy to, że system oparty na licencji GPL jest naj-tańszy. Jeśli nie mamy w zespole samo-dzielnego eksperta od takiego systemu, to musimy się liczyć z koniecznością prze-szkolenia inżynierów oraz wykupieniem pomocy technicznej. W systemach ko-mercyjnych często takie szkolenia są wli-czone w cenę. Ważne jest również, aby ist-niały wydajne i stabilne narzędzia deve-loperskie wspomagające programowanie: od IDE poczynając, na debugowaniu koń-cząc. Licencja GPL posiada dodatkowo in-ne ograniczenia. Na przykład trzeba udo-stępnić wszystkie zmiany wprowadzone w takim kodzie.

Najważniejszym elementem takiego sys-temu jest jednak Scheduler, czyli moduł re-alizujący algorytm zarządzania czasem pro-cesora. To od niego bezpośrednio zależy, w jakiej kolejności i na jakich zasadach po-szczególne taski będą realizowały swoje za-dania. I tutaj właśnie może spotkać nas naj-gorszy z możliwych scenariuszy. Jeśli nasz

software został zaprojektowany tak, aby jak najlepiej wykorzystywać zalety płynące z rozdzielania czasu na podstawie ustalonych priorytetów tasków, to może on błędnie za-działać na systemie, w którym czas jest pro-porcjonalnie dzielony pomiędzy wszyst-kie zadania (Rysunek 2). Śledzenia proble-mów mogących powstać w wyniku takiej zamiany nie życzymy najgorszemu wrogo-wi. Można spędzić wiele dni na analizie po-jedynczego przypadku, aby stwierdzić, że większa część jakiegoś komponentu nadaje się jedynie do przepisania. Bez odpowied-nio dobrych narzędzi do dynamicznej ana-lizy kodu (profiler) lepiej nie zabierać się w ogóle do takiego zadania.

Może się także okazać, że nowy RTOS udostępnia nam o wiele mniejszą liczbę priorytetów tasków niż poprzedni system. Częściowo staniemy przed podobnym pro-blemem jak w poprzednim przypadku, tyle że tutaj mamy kilka dróg do wyboru. Mo-żemy na przykład pogrupować taski w taki sposób, aby zadania zależne od siebie mia-ły różne priorytety. Można również połą-czyć takie zadania w jeden moduł (z jed-nym priorytetem) i samemu dopisać war-stwę pośredniczącą, która będzie zarządza-ła kolejnością wykonania tasków. Będziemy musieli skorzystać z bezpośrednich dyrek-tyw RTOS-a do zmiany stanów poszcze-gólnych tasków. Innym rozwiązaniem jest również napisanie całej warstwy abstrak-cji, która będzie tłumaczyła i symulowała zachowania poprzedniego RTOS-a na no-wym systemie. Każdy RTOS udostępnia ja-kieś własne API i jeśli z niego skorzystamy, to musimy się liczyć z podobnymi proble-mami podczas zmiany systemu. Jeśli nasz software korzysta z sygnałów do synchroni-zacji zadań (na jednym systemie), możemy nadpisać takie wywołania własnymi meto-dami realizującymi ten sam cel za pomo-cą semaforów (na drugim systemie). Dzię-ki temu nie będziemy musieli zmieniać od-wołań w istniejącym kodzie. Warto tutaj dodać, że istnieje standard POSIX dla sys-

temów czasu rzeczywistego i embedded, a więc stosując się do jego wytycznych w na-szej implementacji, będziemy mogli prze-nosić nasz kod pomiędzy różnymi systema-mi zgodnymi z POSIX.

ProcesW czasach, kiedy definiowano modele pro-cesów do produkcji oprogramowania, bar-dziej niż moment pojawienia się nowego produktu na rynku liczył się sam fakt je-go pojawienia. Projektanci miesiącami sta-rali się przewidzieć rozmaite scenariusze i zaprojektować wszystko na papierze z naj-drobniejszymi szczegółami (waterfall). W dzisiejszych czasach takie podejście mo-głoby doprowadzić do opóźnień absolut-nie nieakceptowanych przez rynek i klien-tów. Nie możemy utopić projektu w morzu papierowych dokumentacji i analiz. Dlate-go właśnie powstają coraz to nowe meto-dologie tworzenia oprogramowania, któ-re bardziej wpasowują się w aktualne wy-magania i trendy na rynku. Jednym z przy-kładów może być Agile. Nie należy jed-nak bezkrytycznie stosować się do wszyst-kich jego wytycznych, można wybrać kil-ka praktyk (stand up meetings, scrum, itera-cje) i stosować się do nich. Koniec końców jednak sukces wielkich i ”nieprzewidy-walnych” projektów zawsze w dużej mie-rze opiera się na indywidualnych zdolno-ściach poszczególnych członków zespołu. Pamiętajmy, że proces ma nam pomagać, a nie utrudniać.

Migracja w praktyceW przypadku terminali systemu TETRA mieliśmy i wciąż mamy do czynienia z nie-mal pełnym spektrum prac związanych z przenoszeniem oprogramowania na nowe platformy:

• istniejące radio samochodowe zostało wyposażone w nowy typ panelu przed-niego z kolorowym wyświetlaczem gra-ficznym w miejsce tekstowego;

Rysunek 1. Reprezentacja 4-bajtowej liczby całkowitej w pamięci

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

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

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

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

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

�����������

Rysunek 2. Podstawowa różnica w dzieleniu czasu procesora pomiędzy taski. – Na podstawie regularnych interwałów czasowych (powyżej). – Na podstawie priorytetów (poniżej). Istnieją oczywiście różne odmiany obu idei jak również ich kombinacje

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

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

Page 180: SDJ Extra 34 Biblia

180

Rozwiązania mobilne

SDJ Extra 34 Biblia

• na potrzeby modemu TETRA istniejące radio zostało przeniesione na kartę SD, którą można zainstalować w urządzeniu typu PDA;

• wprowadzenie radiotelefonu w standar-dzie ATEX wiązało się ze zmianą kla-wiatury i zapewnieniem współpracy z elementami obudowy spełniającymi szczególnie rygorystyczne kryteria;

• model Covert obywa się bez wyświe-tlacza, ale dysponuje zdalnym urządze-niem sterującym;

• powstała również całkowicie nowa plat-forma sprzętowa z innymi procesorami i nowym systemem operacyjnym.

NGCH - Next Generation Control HeadPotrzeba posiadania zunifikowanego inter-fejsu użytkownika dla różnych modeli ra-diotelefonu wydaje się naturalna, więc po wprowadzeniu przenośnych radiotelefo-nów MTH800 i MTP850 posiadających kolorowe wyświetlacze i interfejs użytkow-nika podobny do znanego z telefonów ko-mórkowych Motoroli, było jasne, że po-dobna zmiana dotknie samochodowy mo-del MTM800.

Stary panel przedni zaopatrzony w trzy-liniowy tekstowy wyświetlacz służył prak-tycznie tylko do wyświetlania czystego tek-stu i przekazywania do radia informacji o naciśniętych klawiszach. Nowe urządzenie z silnym procesorem Texas Instruments stwarzało o wiele większe możliwości w postaci przeniesienia na nie części funk-cjonalności aplikacji. Należało jednak zna-leźć najodpowiedniejszy punkt przecięcia warstwy aplikacji i połączyć niskie war-stwy oprogramowania z modelu MTM800 z wysokimi warstwami z modeli z wyświe-tlaczem graficznym. Wydatnie pomogła w tym zadaniu modułowa struktura oprogra-mowania, jednak myli się ten, kto sądzi, że wystarczyło trywialne przeniesienie czę-ści kodu.

W praktyce stanęliśmy tu przed proble-mem opisanym kilka akapitów wyżej: ada-ptacją kodu do nowego procesora, kompila-tora i systemu operacyjnego. Częścią prac stało się stworzenie warstwy abstrakcji nad systemem operacyjnym, która umożliwiła w miarę łagodne przejście na nową platfor-mę. Konieczne stało się również opracowa-nie protokołu komunikacyjnego pomiędzy radiem a panelem przednim i serializacja przesyłanych danych (wraz ze zmianą en-dianessu).

Z drugiej strony projekt ten stał się oka-zją do wprowadzenia znaczących uspraw-nień i optymalizacji. Ponieważ ostateczna decyzja o linii podziału software’u pomię-dzy radiem i panelem wskazała jako najlep-sze rozwiązanie oddzielenie ściśle interfej-sowej części tasku aplikacyjnego, można było poprawić separację tego obszaru ko-du i rozbić go, zgodnie z regułami sztuki, na dwa osobne taski. Konieczność stworze-nia dużych ilości nowego kodu pozwoliła wykorzystać techniki modelowania obiek-towego – znaczna część kodu powstała w środowisku Rational Rose RealTime. Nie do przecenienia była również możliwość wyprowadzenia z radia elementów o du-żym zapotrzebowaniu na pamięć: w pa-mięci panelu przedniego znalazły się np. czcionki; pracuje tam również WAP brow-ser, który jest dostarczany jako zewnętrz-na biblioteka.

Równie ciekawym aspektem pracy nad tym projektem były problemy sprzętowe, które się w nim pojawiły (synchronizacja po-między radiem a panelem przednim połączo-nym z nim wielometrowym kablem; zapew-nienie panelowi odpowiedniego napięcia do jego włączenia i wykrycie na radiu sytuacji, w której się to nie udało) oraz nowe dostępne konfiguracje sprzętu: kilka release’ów później wprowadzono na przykład możliwość szere-gowego podłączenia dwóch paneli do jedne-go radia (co może być użyteczne na przykład w karetkach).

Ograniczanie i rozszerzanie – ATEX, Covert i TETRA ModemNiejednokrotnie w parze z wprowadza-niem funkcjonalności dla nowych plat-form idzie ograniczenie funkcjonalności istniejących. Jeśli użytkownik będzie pra-cował w grubych, strażackich rękawicach, liczne drobne klawisze będą dla niego ra-czej przeszkodą niż pomocą. Jeśli radio po-zbawione będzie wyświetlacza, to jak za-pewnić dostępność funkcjonalności, któ-re dotychczas z korzystaniem z wyświetla-cza się wiązały?

• Jednym z wymagań stawianych radio-telefonom pracującym w warunkach szczególnie niebezpiecznych jest ogra-

niczenie maksymalnego poboru prą-du. W tym celu trzeba było w modelu ATEX synchronizować zadania obsługi LCD oraz obsługi nadajnika radiowego, aby nigdy nie wystąpiły jednocześnie. Trudność polegała na tym, że pierwsze z nich jest asynchroniczne, a drugie ple-zjochroniczne.

• W przypadku radiotelefonu kamuflo-wanego (covert radio) konieczne było maksymalne uproszczenie interfejsu użytkownika poprzez eliminację wy-świetlacza i zapewnienie sterowania przez zdalnego pilota. Głównym pro-blemem było tu usunięcie wszelkich interakcji z użytkownikiem, które po-legały na dialogu (np. żądanie nume-ru PIN) – w zamian pozostawiono tyl-ko te funkcje, które mogły być wyko-nane natychmiast (jedyna informacja zwrotna dostępna była jako dźwięk w słuchawce).

• Użycie protokołu tetrowego na PDA zmusiło nas do odseparowania części lo-giki od pozostałej funkcjonalności. En-kapsulacja modułu ściśle zależnego od otoczenia nie jest największym wyzwa-niem dla programisty, ale z pewnością wymaga dużej ostrożności i dokładne-go przebadania zależności w istniejącym kodzie.

Gdy osiągnięto granice możliwościChoć nieodłączną częścią programowania urządzenia mobilnego jest optymalizacja istniejącej implementacji w celu pomiesz-czenia kolejnych funkcjonalności na tej sa-mej platformie sprzętowej, zawsze istnieć będą ograniczenia, których żadną imple-mentacją ominąć się nie da. Można wów-czas pójść ścieżką wymiany poszczegól-nych elementów hardware’u w celu za-chowania maksymalnej kompatybilności, ale można też postawić na zupełnie nową, tworzoną od podstaw, platformę, która w dłuższej perspektywie czasowej zastąpi modele istniejące. I jest to kolejna ze ście-żek rozwoju oprogramowania radiotelefo-nów TETRY, którą podążyliśmy.

Nawet jeśli wymianie ma ulec niemal cały sprzęt radia, nie oznacza to jeszcze, że pro-ces tworzenia oprogramowania należy roz-począć od nowa. Wręcz przeciwnie – migra-cja software’u na nową platformę jest znako-mitą okazją na poprawienie separacji kodu i uporządkowanie go po procesie wielolet-niego developmentu. Udoskonalenia wpro-wadzone w ramach tego procesu mogą być równie pożyteczne dla starych platform, jak i dla nowej.

Największa migracja, jaką wykonaliśmy w krakowskiej Motoroli, była jednocześnie zmianą kompleksową, polegającą na grun-Rysunek 3. ATEX MTP850Ex

Page 181: SDJ Extra 34 Biblia

Stary software – nowa platforma

townym przemodelowaniu architektury ra-dia, i obejmującą:

• wprowadzenie nowych procesorów, za czym podążyła konieczność separacji ta-sków dotychczas pracujących na jednym procesorze;

• wraz z nowymi procesorami pojawił się nowy system operacyjny, posiadają-cy mechanizmy systemu czasu rzeczy-wistego, którymi nie dysponowaliśmy wcześniej, ale też nie do końca zgodny z naszym systemem w zakresie prioryte-tów tasków;

• przepisanie części assemblerowej na C przy jak najmniejszej utracie wydajności;

• wprowadzenie mechanizmów szybkiego reagowania i wczesnego wykrywania po-trzeb danego taska zamiast przygotowy-wania wszystkich ewentualnych odpo-wiedzi kosztem zwiększonego zapotrze-bowania na prąd;

• zaimplementowanie sterowników obsłu-gujących dotychczasowe akcesoria, jak również przygotowanych na przyszłych następców.

PodsumowanieZawsze, gdy pojawia się na horyzoncie pro-jekt związany w rozszerzaniem nowej plat-

formy, developerzy i projektanci snują swo-je wizje reorganizacji istniejącego kodu, gruntownego refaktoringu, zmiany prze-starzałego designu i prawie zawsze spoty-kają się z jedną odpowiedzią ze strony kie-rownictwa: Rewolucji nie będzie. Tylko nie-zbędna ewolucja. I chociaż dla tych pierw-szych właśnie zawalił się świat, to w rze-czywistości właśnie droga ewolucji pozwa-la śledzić wpływ wprowadzonych zmian na wielokrotnie przetestowaną i napra-wianą funkcjonalność końcową. Stara zasa-da mówi, że nie zmienia się drużyny, która wygrywa. Dla kodu dopieszczanego i testo-wanego przez kilka lat i tysiące użytkow-ników rewolucja nie jest pierwszą rzeczą, którą należy brać pod uwagę. Czasami jed-nak wyjścia już nie ma.

Mimo iż przenoszenie istniejącego opro-gramowania na nową platformę sprzętową czy też nowy system operacyjny nie jest za-daniem trywialnym, to szczerze polecamy taką lekcję każdemu programiście (a już na pewno tym lubiącym wyzwania). Spek-trum problemów, z którymi będziemy się musieli zmierzyć, zmusi nas do bardzo do-głębnej analizy zagadnień znanych jedynie ze studiów bądź literatury. Poznamy rów-nież narzędzia wspomagające taką tranzy-cję lub sami takie napiszemy. Bez wątpie-nia doświadczenie zdobyte w takim pro-jekcie uczyni nasz warsztat bardziej doj-rzałym i przemyślanym oraz przyczy-ni się do wzrostu naszej wartości na ryn-ku pracy.

KAMIL KOWALSKIWykształcenie w dziedzinie telekomunikacji, związany z programowaniem radiotelefonów Motoroli od prawie 5 lat. Odpowiedzialny za rozwijanie protokołu komunikacji z infrastrukturą.Kontakt z autorem: [email protected]

ARTUR CHRUŚCIELZ wykształcenia informatyk, w Motoroli pracuje od 2005 roku. Od początku w zespole tworzącym oprogramowanie dla radiotelefonów systemu TETRA. Zajmuje się głównie warstwami związanymi z protokołem komunikacji po interfejsie radiowym w trybie DMO (bez infrastruktury).Kontakt z autorem: [email protected]

R E K L A M A

Page 182: SDJ Extra 34 Biblia

182

Rozwiązania mobilne

SDJ Extra 34 Biblia

Oracle SPATIAL

www.sdjournal.org 183

Ogromna popularność takich serwi-sów jak Google Maps, Google Earth, MS Virtual Earth niewątpliwie

przyczyniła się do wzrostu zapotrzebowania na systemy do obsługi różnych typów danych przestrzennych. Praktycznie każdy z nas ma do czynienia z GISem, korzystając z nawigacji samochodowej bądź planując trasy za pomo-cą różnych serwisów internetowych lub wy-szukując informacje o lokalizacji interesujące-go nas obiektu. Można stwierdzić, że dokonała się ewolucja, a GIS z wyspecjalizowanych sys-temów, dostępnych tylko wąskiej grupie użyt-kowników, stał się ogólnodostępny.

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

• punkt;• linia;• poligon;• + warianty wymienionych

• multipunkt;• multilinia;• multipoligon;

a także od wersji 11g bryły

• solid;• composite solid.

Istnieje cały zestaw sztywnych reguł zwią-zanych z tworzeniem obiektów, które mu-szą zostać spełnione, aby obiekt był po-prawny, a takie procedury jak SDO _

GEO M.VA LIDATE _ GEO M ETRY _ WITH _

CONTEXT bądź SDO _ GEOM.VALIDATE _

LAYER _ WITH _ CONTEXT umożliwiają zdia-gnozowanie nieprawidłowości, natomiast SDO _ UTIL.RECTIFY _ GEOMETRY służy do automatycznej naprawy niespełniające-go reguł poprawności obiektu geometrycz-nego.

Na obiektach geometrycznych możemy wykonywać proste operacje obliczeniowe:

• zwróć długość;• zwróć powierzchnie;• zwróć objętość(dla brył),

bądź bardziej złożone, np:

• przecinanie;• łączenie;• zwracanie centroidu;• upraszczanie, czyli generalizacja.

„Układy współrzędnych”Każdy obiekt geometryczny powinien być opa-trzony informacją o układzie współrzędnych, w którym się znajduje tzw. SRID(System Reference ID). Jest to szczególnie istotne dla poprawno-ści obliczeń prowadzonej dla obiektów znajdu-jących się w układach odniesienia opartych na geoidzie(czyli kuli ziemskiej).

Od wersji 10gR2 bazy danych, wszyst-kie definicje układów odniesienia są za-pisane w standardzie EPSG, predefinio-wanych jest 4402 układów (select co-unt(1) ile from MDSYS.sdo_cs_srs). Wy-mienię kilka z nich, które używane są w Polsce : PUWG 2000(SRID od 2176 do 2179), PUWG 1992(SRID=2180), WGS84(SRID=8307).

Oracle SPATIAL

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.

Dowiesz się:• Czym są systemy GIS;• Jakie mamy rodzaje danych przestrzennych.

Powinieneś wiedzieć:• Podstawy SQL.

Poziom trudności

Opis podstawowych funkcjonalności opcji Oracle SPATIAL dedykowanej dla systemów GIS

Rysunek 1. Rodzaje obiektów przechowywanych w SDO_GEOMETRY

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

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

�����������

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

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

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

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

Page 183: SDJ Extra 34 Biblia

182

Rozwiązania mobilne

SDJ Extra 34 Biblia

Oracle SPATIAL

www.sdjournal.org 183

Istnieje możliwość zdefiniowania własne-go układu współrzędnych, jeśli definicja zo-stanie wprowadzona poprawnie, można dy-namicznie bądź statycznie przekształcać obiekty z jednego układu współrzędnych na inny, używając pakietów SDO_CS.TRANSFORM bądź SDO_CS.TRANSFORM_LAYER.

Nowością w wersji 11g jest wprowadzenie układów współrzędnych 3D.

Wydajność – indeksy przestrzenne i partycjonowanieIndeks przestrzenny, obecnie typu R-TREE, budowany jest w oparciu o MBR(Minimum Bounding Rectangle) indeksowanego obiek-tu przestrzennego, następnie dokonywana jest agragacja do wyższego poziomu. Może-my sterować parametrami indeksu, np: czy ma być budowany tylko w oparciu o współrzęd-ne XY obiektu, czy także indeksowaniu pod-lega współrzędna Z(SDO_INDX_DIMS=3); bądź w na jakiej wielkości paczkach zatwierdzonych nowych/zmienionych indeksowanych elemen-tów ma być dokonana przebudowa (SDO_DML_BATCH_SIZE=1000).

Od wersji 11g istnieje możliwość partycjo-nowania tabel zawierających obiekty geome-tryczne. Tworzony jest tzw. SDO_ROOT_MBR dla 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 przestrzenneWykonywane za pomocą języka SQL, SPA-TIAL wzbogaca standardowego SQL’a o moż-liwości przestrzenne. W przykładzie poka-zano analizę przestrzenną ze złożeniem kli-ku relacji przestrzennych w jednym zapyta-niu SQL.

SELECT /*+ordered*/ D.Geometry, D.obreb, D.nr

FROM obszary_zagrozen Z, dzialki D

WHERE Z.typ = ‘S’

AND SDO_RELATE(D.Geometry, Z.Geometry,

‘mask=touch+coveredby’) = ‘TRUE’;

Należy pamiętać o właściwej kolejności po-dania obiektów dla funkcji SDO _ REALATE, jako pierwszy parametr podana jest zaindek-sowana kolumna tabeli w której znajdują się wyszukiwane obiekty, natomiast drugim pa-rametrem(zawiera obiekt/obiekty wyszuku-jące) może być podany jawnie pojedynczy obiekt SDO _ GEOMETRY lub kolumna typu SDO _ GEOMETRY.

Dostępne analizy wykorzystują następujące operatory przestrzenne:

• - Inside - Contains• - Touch - Disjoint• - Covers - Covered By• - Equal - Overlaps

Operator odległości

• - Within Distance (funkcja SDO _

WITHIN _ DISTANCE)

Możliwa jest również analiza sąsiedztwa

• - Nearest Neighbor (funkcja SDO _ NN)

Wszystkie analizy wykonywane są dwuetapo-wo, najpierw na podstawie przeszukiwania in-deksu przestrzennego wybierani są kandydaci, a następnie wykonywana jest szczegółowa ana-liza geometryczna i zwracany wynik.

Odstępstwem od powyższej reguły jest funkcja SDO_FILTER, która przeznaczona jest do bardzo szybkiego wyszukiwania obiektów np: w celu ich przekazania do wizualizacji. Funkcja jako wynik zawraca zawsze wszyst-kich kandydatów, nie wykonując drugie-

go, kosztownego czasowo, etapu anlizy prze-strzennej.

Wszystkie analizy działają również dla da-nych 3D i uwzględniają specyfikę związaną z układami współrzędnych.

LRS – liniowy system referencyjnyDo działania wymaga obiektów geometrycz-nych liniowych z wypełnionym przynajm-niej dla początku i końcu odcinka parame-trem M(w poniższym przykładzie będzie to kilometraż). Funkcjonalność znajduje za-stosowanie np. do ewidencjonowania infra-struktury drogowej, wykorzystywana jest do obsługi zjawisk o charakterze liniowym bądź punktowym. Na przykład chcąc uzy-skać odcinek drogi, na którym obowiązu-je ograniczenie prędkości na segmencie od 53 do 59 kilometra, wywołujemy funkcję SDO_LRS.CLIP_GEOM_SEGMENT z odpowiedni-mi parametrami, która wycina i zwraca z ca-łego odcinka drogi tylko segment od 53 do 59 kilometra. Korzyści: geometrię przecho-wujemy tylko raz, natomiast wszystkie geo-metrie pochodne tworzone są na zasadzie dy-namicznej segmentacji geometrii bazowej na podstawie dostarczonych atrybutów. Możli-

Rysunek 2. Rodzaje obiektów 3D

�����������

� �

� �

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

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

�����

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

��������

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

Rysunek 3. Przykłady relacji przestrzennych

Page 184: SDJ Extra 34 Biblia

184

Rozwiązania mobilne

SDJ Extra 34 Biblia

wa jest również sytuacja odwrotna – poda-jemy współrzędne na odcinku, a otrzymuje-my kilometraż.

Numeryczny model terenu – TIN, LIDAR

• LIDAR – chmura punktów, z bardzo du-żą dokładnością odwzorowuje powierzch-nię, posiada natomiast tę niedogodność, że wolumen danych liczony jest w milionach punktów na odwzorowywany obiekt. Stworzono specjalną funkcjonalność do zarządzania tym rodzajem danych(SDO _

POINT _ CLOUD). Dane są agregowane, kom-presowane i indeksowane. Dostęp do nich jest bardzo szybki. Dostarczany jest rów-nież konwerter do czytania danych(for-mat LAS)z urządzeń pomiarowych.

• TIN – model terenu w postaci nieregu-larnej siatki trójkątów, opcja SPATIAL udo-stępnia również zestaw pakietów służą-cych budowie TIN’a. Można dokonać przekształcenia SDO _ POINT _ CLOUD na SDO _ TIN.

Topologia trwałaRelacyjny sposób zapisu warstwy poligonowej, charakteryzuje się jednokrotnym przechowy-waniem współrzędnych wspólnych punktów dla sąsiadujących poligonów. Zapewnia spój-ność i ciągłość warstwy geometrycznej. Nie-które analizy przestrzenne (np. najbliższego są-siedztwa) wykonywane są na warstwie topolo-gicznej znacznie szybciej, niż na warstwie geo-metrycznej. Możliwe jest również budowanie zależności hierarchicznych pomiędzy obiekta-mi w celu utrzymania spójności granic obiek-tów nadrzędnych dziedziczących kształt z za-gregowanych obiektów podrzędnych. Funkcjo-nalność dedykowana dla systemów katastral-nych.

Modele siecioweNetwork Data Modeling(NDM) – funkcjonal-ność do obsługi grafów sieci.

NDM może przechowywać model sieci bez odniesienia przestrzennego(logiczny) al-bo model z reprezentacją geometryczną. Mo-del z geometrią może być dodatkowo zaopa-trzony w LRS(Linear Referencing System)

NDM obsługuje następujące obszary funk-cjonalne:

• składowanie modelu sieciowego w struk-turze tabelarycznej powiązanej relacjami;

• wsparcie dla edycji, kontroli poprawno-ści i optymalizacji struktury;

• wbudowane analizy sieciowe(najkrótsza ścieżka, problem komiwojażera, niedo-stępność grafu, wszystkie ścieżki itp.);

• możliwość dodawania własnych reguł.

W wersji 11g pojawiła się nowa funkcjonal-

ność (Load On Demand – NDM LOD) de-dykowana do obsługi bardzo dużych grafów sieci(złożoność liczona w dziesiątkach milio-nów elementów). Jest w stanie wydajnie ob-sługiwać ogromne ilości analiz sieciowych w czasie zbliżonym do rzeczywistego. Stanowi narzędzie do budowy systemów zarządzają-cych obliczeniami na złożonych modelach sieciowych, np. SmartGrid

NDM LOD – wprowadza istotne zmiany w stosunku do samego NDM; najważniejszą jest możliwość wykonywania wszystkich analiz sie-ciowych na partycjonowanym – podzielonym na klastry modelu sieciowym(niezależnie od opcji partycjonowania do bazy danych). Kolejne funkcjonalności, które warto wymienić, to:

• analiza wielokosztowa;• możliwość wyniesienia obliczeń poza

bazę danych;• obsługa bufora na zasadzie kolejki LRU.

W kolejnych wersjach Oracle SPATIAL rozwi-jana będzie tylko funkcjonalność NDM LOD.

GeorasterZestaw funkcjonalności służący do składowa-nia i przetwarzania obrazów rastrowych.

Największe instalacje przechowują tera-bajty danych i realizują kilkaset żądań na se-kundę.

Składowane dane są dzielone na bloki i kompresowane(kompresja stratna JPEG-B, JPEG-F; bezstratna – deflate(zip)), budowa-ny jest także dedykowany dla obrazów in-dex piramidalny. Można zmieniać kryteria podziału rastra na bloki oraz liczbę pozio-mów indeksu piramidalnego, jest to jeden z elementów optymalizacji przechowywania i dostępu do georastrów. Na obrazach może-my dokonywać georeferencji, filtrować, do-pisywać własne atrybuty, wycinać. Szcze-gółowy opis funkcjonalności w obszernym dokumencie, który dostępny jest pod ad-resem: http://www.oracle.com/pls/db111/to_pdf?pathname=appdev.111/b28398.pdf

AdnotacjeSłuży do przechowywania tekstu i jego atry-butów(oznaczenia obiektów np: numeracja, budynków, nazwy ulic, itp.). Składowane są następujące informacje:

• tekst;• sposób prezentacji tekstu(czcionka, ko-

lor, odstępy);• położenie tekstu(punkt wstawienia, kie-

runek, linia wiodąca);

Istnieje możliwość tworzenie opisów złożo-nych składających się np: z licznika mianow-nik i kreski ułamkowej.

Typ danych zaimplementowany zgodnie ze specyfikacją OGC opisaną w dokumencie

„OpenGIS Implementation Specification for Geographic information – Simple feature ac-cess – Part 1: Common architecture”.

Workspace managerZarządza pracą grupową, wspiera następują-ce funkcjonalności:

• zarządzanie przywilejami;• blokowanie obiektów;• multiversjonowanie;• operacje na przestrzeniach roboczych;• detekcja i rozstrzyganie konfliktów;

Umożliwia obsługę danych historycznych i udostępnienie danych aktualnych na precy-zyjnie określony moment w czasie.

Udostępnianie danych – serwisy WWW OGCOpcja SPATIAL udostępnia następujące ser-wisy WWW zgodne ze specyfikacją OGC:

• WFS;• WFS-T;• CSW;• Geocoding;• Routing;• OpenLS.

Serwis WMS realizowany jest w produkcie MapViewer, posiadającym silnik renderują-cy i służącym do wizualizacji danych prze-strzennych oraz tworzenia aplikacji.

Współpraca z oprogramowa-niem GIS innych producentówDługa obecność na rynku oraz wykorzystanie przez ORACLE SPATIAL standardów OGC sprawiły, iż sposób zapisu danych przestrzen-nych stworzony przez ORACLE jest obsługi-wany przez oprogramowanie firm: AUTO-DESK, BENTLEY, ESRI, INTERGRAPH, MAPINFO, SMALLWORLD(GE) i innych.

Lista partnerów technologicznych pod adresem: http://www.oracle.com/technology/products/spatial/spatial_partners_isv.htm

Dokumentacja produktu w internecieArtykuł w sposób bardzo skrótowy przedsta-wia podstawowe funkcjonalności opcji Orac-le Spatial. Szczegółowa dokumentacja pro-duktu dostępna jest w Internecie na stronach OTN pod adresem:

http://www.oracle.com/pls/db111/portal.portal_db?selected=7&frame=#oracle_spatial_and_loca-tion_information.

TOMASZ MURTAŚPracuje w Oracle Polska jako Principal Sales Con-sultant, Eastern Europe.Kontakt z autorem: [email protected]

Page 185: SDJ Extra 34 Biblia
Page 186: SDJ Extra 34 Biblia

186

Rozwiązania mobilne

SDJ Extra 34 Biblia

Wzorce projektowe w Java Card

www.sdjournal.org 187

Jak ważne jest właściwe prowadzenie pro-jektów informatycznych ściśle po torach wyznaczanych przez metodologie two-

rzenia oprogramowania, wiadomo nie od dziś. Nawet dla najmniejszych komponentów, budu-jących większe systemy, niezbędne jest tworze-nie modelu, który pozwala m.in.: na spojrzenie na kod z perspektywy wymagań systemu już od samego początku jego tworzenia, łatwiejszą we-ryfikację, testowanie oraz integrację poszczegól-nych części oprogramowania.

Podobnie jak istnienie projektu dla tworzo-nego oprogramowania, równie istotnym ele-mentem procesu jego budowy jest sama jakość wytwarzanego kodu. Można przytoczyć szereg ogólnych rad, tzw. best practices, funkcjonujących w środowisku programistów, które pozwalają na osiągnięcie wysokiej jakości kodu. Stosowanie się do tych praktyk czy wytycznych przez dewelo-perów jest szczególnie ważne przy programowa-niu w technologii Java Card. Możliwości aktuali-zacji apletu nagranego na kartę i udostępnione-go użytkownikowi są praktycznie żadne, a po-pełnianie błędów w implementacji może w naj-gorszym przypadku doprowadzić do nieodwra-calnego uszkodzenia karty. Ostrzeżenia o ko-nieczności dokładnego stosowania się do zale-ceń, np. odnośnie użycia pamięci w Java Card,

pojawiają się czerwoną, pogrubioną czcionką już we wstępie do Java Card™ & STK Applet Deve-lopment Guidelines – dokumentu dostarczanego przez jednego z producentów kart SIM.

Wytyczne dla programistów Java CardNajbardziej podstawowe zalecenia wymienia-ne we wspomnianych Development Guideli-nes, jakimi powinien kierować się programi-sta Java Card, dotykają zarówno kwestii ści-śle powiązanych z samą technologią (mode-lem pamięci, programowaniem SIM Applica-tion Toolkit - STK), jak i ogólnie przyjętych za-sad związanych z tworzeniem bezpiecznych aplikacji oraz czysto inżynierskimi praktyka-mi tworzenia oprogramowania. Do rekomen-dacji wynikających wprost ze stosowania tech-nologii Java Card można zaliczyć:

• stosowanie modyfikatora static dla za-inicjalizowanych buforów danych (prze-chowujących na przykład teksty wy-świetlane w komunikatach STK),

• obsługa współbieżności (tzw. reentrance) w STK, czyli zwrócenie uwagi, że sesja STK zainicjowana przez jedną aplikację STK może zostać przerwana na czas wykony-wania sesji zainicjowanej przez inną apli-kację,

• stosowanie się do zaleceń opisanych w dokumentach 3GPP 31.130 ((U)SIM API) i/lub TS 101476 (GSM API) doty-czących dostępności określonych obiek-

tów typu STK Handler w określonych momentach życia aplikacji STK,

• rozważne wykorzystanie globalnych i lo-kalnych zmiennych (tj. w szczególności: unikanie redundancji, minimalizacja liczby zmiennych lokalnych, minimali-zacja liczby parametrów metod itd.),

• zakaz przechowywania referencji do tzw. Entry Point Object, jakim jest na przykład obiekt klasy APDU.

Z kolei, z punktu widzenia bezpieczeństwa tworzonego apletu, deweloperzy powinni stosować się do następujących zaleceń:

• unikanie przechowywania kluczy krypto-graficznych oraz kodów PIN w tablicach prymitywnych typów, np. tablicy bajtów,

• ograniczenie czasu życia poufnych da-nych do czasu trwania sesji, w której są one wykorzystywane (np. poprawna ob-sługa tworzenia i kasowania kluczy se-syjnych w aplecie),

• przechowywanie poufnych danych w ta-blicach typu transient,

• ochrona danych przed atakiem typu rol-lback, związanym z występowaniem w API Java Card mechanizmu transakcji (po dokładny opis odsyłamy do Java Card & STK Applet Development Guidelines).

Pierwszym i najprostszym krokiem do za-spokojenia wyżej wymienionych wyma-gań jest z pewnością kierowanie się dobry-mi praktykami programistycznymi. Zale-cenia dla programistów Java Card są tutaj praktycznie identyczne jak dla inżynierów pracujących z wykorzystaniem innych tech-nologii. Przede wszystkim podkreśla się ko-nieczność:

• wykorzystywania metod API Java Card wszędzie, gdzie jest to możliwe, zamiast wprowadzania własnych implementacji,

Wzorce projektowe w Java CardTworzenie 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.

Dowiesz się:• o dobrych praktykach programowania w Ja-

va Card;• Jak dostosować istniejące wzorce projekto-

we do specyfiki platformy Java Card;• Jak tworzyć kod Java Card, który można te-

stować przy użyciu testów jednostkowych;

Powinieneś wiedzieć:• Znać podstawy programowania dla platfor-

my Java Card; • Posiadać wiedzę o projektowaniu oprogra-

mowania;• Znać język UML.

Poziom trudności

Page 187: SDJ Extra 34 Biblia

186

Rozwiązania mobilne

SDJ Extra 34 Biblia

Wzorce projektowe w Java Card

www.sdjournal.org 187

• tworzenia krótkich metod, co poprawia czytelność i pielęgnowalność kodu,

• nie przekraczanie liczby 256 metod na klasę z uwagi na ograniczenia pamięci EEPROM oraz także ze względu na po-prawność projektu apletu (należy mieć na względzie separację odpowiedzialno-ści klas w aplecie).

Wzorce projektoweJednym 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-

rzystywać, projektując i implementując opro-gramowanie dla kart inteligentnych. W dal-szej części artykułu przyjrzymy się, jakie wzor-ce można z powodzeniem stosować w apletach Java Card oraz jak wygląda ich implementacja,

gdy weźmiemy pod uwagę ograniczenia wyni-kające z zastosowania tej technologii.

Do przedstawienia kolejnych wzorców, jakie mogą być wykorzystane w apletach Java Card posłuży, zaprezentowany na Rysunku 1, dia-

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

������

������

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

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

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

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

�����������

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

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

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

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

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

��������

�����

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

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

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

���������

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

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

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

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

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

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

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

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

�������

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

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

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

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

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

Rysunek 2. Diagram sekwencji wywołania komendy zmiany kodu PIN

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

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

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

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

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

������

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

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

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

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

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

����������

Page 188: SDJ Extra 34 Biblia

188

Rozwiązania mobilne

SDJ Extra 34 Biblia

Wzorce projektowe w Java Card

www.sdjournal.org 189

gram przygotowany w języku UML, ilustrujący niepełny projekt pewnego apletu. Niniejszy ar-tykuł został podzielony według przyjętej ogólnie kategoryzacji, która wyróżnia wzorce kreacyjne, strukturalne oraz behawioralne. Wyróżnia się tak-że kategorię wzorców wykorzystywanych w pro-

gramowaniu współbieżnym, jednak w technolo-gii Java Card nie mają one zastosowania.

Wzorce behawioralneJednym ze sposobów komunikacji z apletem Java Card jest komunikacja z wykorzystaniem

pakietów APDU (Application Protocol Data Unit). Zachowanie apletu jest wtedy determi-nowane poprzez wartość określonego bajtu (nazywanego w specyfikacji ISO7816-4 INS). Rozpowszechnioną praktyką (obecną nieste-ty nawet w przykładach kodu udostępnianych przez producentów kart, takich jak Gemal-to) jest użycie tego bajtu do budowy instruk-cji switch w metodzie przetwarzającej aple-tu, która wywołuje dalej kod odpowiednich operacji. Dodatkowo, większość tych operacji przetwarza dane dostarczone w pakiecie AP-DU (w polu Data).

Tymczasem taka sytuacja doskonale nadaje się do wdrożenia wzorca komendy. Na podsta-wie przychodzącego do apletu APDU tworzo-ny jest odpowiedni obiekt komendy (zgodnie z wartością bajtu INS), którego metoda run jest następnie wywoływana z odpowiednimi ar-gumentami w celu dokonania przetwarzania. Diagram sekwencji takiego wywołania przed-stawiony został na Rysunku 2.

Interfejs Command przedstawiony na dia-gramie klas z Rysunku 1 definiuje interfejs wszystkich komend, które ma do dyspozy-cji tworzony aplet. Rzeczywiste komendy implementują ten interfejs, definiując kon-kretne czynności, które są wykonywane w ramach danej komendy.

Wprawne oko zauważy jeszcze jeden wzo-rzec, który został wykorzystany przy budo-wie komend przykładowego apletu. Chodzi tutaj o wzorzec template method, który został wykorzystany do zapewnienia uwierzytelnie-nia dla metod dziedziczących po komendzie AuthorizedCommand. Komenda ta implemen-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-trakcyjnej metody runAuthorized, której im-plementacja jest dostarczana przez konkret-ne klasy dziedziczące po AuthorizedCommand. 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 kreacyjneW 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, zastosowane fabryki muszą jednocześnie dzia-

Listing 1. Klasa AuthorizedCommand

package pl.mobileexperts.sdj.samples;

/**

* Root class for commands which need PIN authorization.

*/

public abstract class AuthorizedCommand implements Command {

/**

* Execute the command.

*

* @return number of bytes written to output buffer

*/

public final short run(Token token, byte[] buffer, short length,

short offset) {

short size = 0;

byte pinLength = buffer[(short) (offset + length - 1)];

if (token.verifyPIN(buffer, (short) (offset + length - pinLength - 1),

pinLength)) {

try {

size = runAuthorized(token, buffer,

(short) (length - pinLength - 1), offset);

} finally {

token.logout();

}

} else {

ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);

}

return size;

}

/**

* Perform normal command functioning after PIN authorization.

*/

protected abstract short runAuthorized(Token token, byte[] buffer,

short length, short offset);

}

Rysunek 3. Organizacja i zarządzanie pamięcią typu TLV

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

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

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

�� � �� � �� �

�� � �� �

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

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

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

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

Page 189: SDJ Extra 34 Biblia

188

Rozwiązania mobilne

SDJ Extra 34 Biblia

Wzorce projektowe w Java Card

www.sdjournal.org 189

łać jako repozytoria obiektów (tzw. object pool), gdyż z założenia, cała pamięć wymagana przez aplet powinna zostać zaalokowana podczas jego instalacji (we wspomnianych wyżej Development Guidelines znajduje się m.in. zalecenie: ALL ob-jects making up the application are created during installation). Dlatego też fabryka komend (Com-mandFactory) budowanego apletu przechowu-je referencje do wszystkich wykorzystywanych przez aplet komend i udostępnia mu je zgodnie ze wzorcem fabryki obiektów.

Wspomniane ograniczenia, wynikające z modelu zarządzania pamięcią w Java Card, wymuszają także, aby fabryka komend dzia-łała jako singleton. Szczególnie w przypadku, gdyby interfejs fabryki został rozszerzony o kolejne metody, bardzo wygodna jest możli-wość pobrania referencji do fabryki z dowol-nego miejsca kodu bez potrzeby odwoływa-nia się do innych obiektów, które taką refe-rencję mogłyby przechowywać.

Wzorce strukturalneWzorzec strukturalny, jaki zastosowany zo-stał w przykładowym aplecie, wynika niejako ze specyfiki technologii Java Card. Dostęp do operacji na kodach PIN, zarządzanie kluczami czy wykonywanie operacji kryptograficznych odbywa się przez niskopoziomowe API defi-niowane przez specyfikację Java Card (obiekty typu OwnerPIN, Cipher itp.) Aplikacje korzy-stające z apletu z reguły wykorzystują jedynie określoną część tych funkcjonalności. Dodat-kowo, bezpośrednie wykorzystanie niskopo-ziomowego API zawsze zaciemnia kod i utrud-nia jego analizę i późniejszą pielęgnację. Natu-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-tek. W projekcie apletu założono, że obiekty klasy Token przesłaniają niskopoziomowe API Java Card służące m.in. do operacji przeprowa-dzania operacji kryptograficznych. Obiekty klasy Token mogą być utożsamiane z wirtual-nymi i hermetycznymi miejscami na karcie in-teligentnej, w których to przechowywany jest materiał kryptograficzny, i które pozwalają na wykonywanie określonych operacji (analogicz-nie do pojęcia tokenu zdefiniowanego w spe-cyfikacji RSA PKCS#11 dla bibliotek krypto-graficznych).

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-rzystości ich kodu. Przykładowo, jak już po-kazano wcześniej, zmiana kodu PIN to wy-wołanie odpowiedniej metody na obiekcie klasy Token, który to dopiero operuje już bez-pośrednio na API Java Card. Takie podejście pozwala na dodatkową strukturalizację i her-metyzację poszczególnych elementów budu-jących aplet, co wpływa na poprawę jakości wytwarzanego kodu.

Innym przykładem zastosowania wzorca fa-sady w programowaniu Java Card jest wyko-rzystanie w kodzie apletu obiektu klasy de-dykowanej do zarządzania pamięcią. Jak już zostało wspomniane wcześniej, programu-

jąc w Java Card spotykamy się z zaleceniem, aby cała pamięć potencjalnie wykorzystywa-na przez aplet była zaalokowana podczas jego instalacji. Należy również pamiętać o braku mechanizmu typu garbage collector. Zachodzi

Listing 2. Klasa ChangePINCommand

package pl.mobileexperts.sdj.samples;

import javacard.framework.ISO7816;

import javacard.framework.ISOException;

import javacard.framework.Util;

/**

* Command used to change PIN. Requires at least two bytes of data.

* Last byte indicates new PIN length.

*/

public final class ChangePinCommand extends AuthorizedCommand {

protected short runAuthorized(Token token, byte[] buffer, short length,

short offset) {

// at this moment current PIN was already authorized

if (length < 2) {

ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

}

// new PIN length

byte pinLength = buffer[(short) (offset + length - 1)];

if (pinLength < TokenConstants.MIN_PIN_SIZE ||

pinLength > TokenConstants.MAX_PIN_SIZE) {

ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

}

// check if received data has correct length

if ((short) (length - 1) != Util.makeShort((byte) 0, pinLength)) {

ISOException.throwIt(ISO7816.SW_WRONG_DATA);

}

// change PIN

token.changePin(buffer, offset, pinLength);

return 0;

}

}

Listing 3. Klasa Token

package pl.mobileexperts.sdj.samples;

import javacard.framework.ISO7816;

import javacard.framework.ISOException;

import javacard.framework.OwnerPIN;

/**

* An abstraction of a cryptographic token, storing keys and PINs.

*/

public class Token implements TokenConstants {

private boolean pinInitialized;

private OwnerPIN pin;

public Token() {

pin = new OwnerPIN(MIN_PIN_SIZE, MAX_PIN_SIZE);

// ...

}

public boolean verifyPin(byte[] buffer, short offset, byte length) {

return pinInitialized && (pin.isValidated() ||

pin.check(buffer, offset, length));

}

public void logout() {

pin.reset();

}

// ...

}

Page 190: SDJ Extra 34 Biblia

190

Rozwiązania mobilne

SDJ Extra 34 Biblia

więc potrzeba elastycznego dostępu i zarzą-dzania pamięcią tak, aby była ona wykorzy-stana w sposób optymalny. Najbardziej efek-tywnym sposobem rozwiązania tego proble-mu jest stworzenie obiektu zarządcy pamię-cią, co może być widziane także jako zastoso-wanie wzorca puli zasobów.

Przykładem implementacji takiego za-rządcy jest wykorzystanie mechanizmu list TLV (tag – length – value). W ten sposób ła-two utrzymać ciągłość pamięci i przydzielać ją w optymalny sposób. Obiekty, które korzy-stają z zasobów pamięci, posługują się jedy-nie identyfikatorem (tzw. tagiem) obiektów

przechowywanych przez zarządcę pamięci, poprzez który zapisane dane są udostępnia-ne. Zastosowanie list TLV zostało pokazane na przykładzie usuwania obiektu z pamięci na Rysunku 3.

Czy warto?Czy warto stosować wzorce projektowe, pro-gramując w technologii Java Card? Z pewno-ścią tak! Oprócz zysków z punktu widzenia procesu wytwarzania systemów informatycz-nych, przy takim podejściu programuje się po prostu wygodniej i przyjemniej. Kod jest czy-telny i przejrzysty, a znalezienie miejsca im-plementacji określonej funkcjonalności nie wymaga uważnego wpatrywania się w ekran podczas przewijania setek linii jednej wielkiej instrukcji switch... Cały aplet wygląda zgrab-nie i, co najważniejsze, działa!

Poza aspektami estetycznymi pojawia się jeszcze kilka dodatkowych zalet. Po pierw-sze, tak napisany kod apletu łatwo poddaje się testowaniu z wykorzystaniem bibliotek typu JUnit. Konieczność dokładnego testowania kodu jest równie istotna, jak wszystkie inne czynności podczas całego procesu tworzenia oprogramowania, o których była mowa wcze-śniej. Test komendy stworzonej dla przykła-dowego apletu jest widoczny we fragmencie Listingu 4. Takie testy pozwalają na stosowa-nie sprawdzonego i uznanego podejścia test-driven development i pokrycie testami więk-szości logiki tworzonego appletu.

Jest oczywiste, że stosowanie dobrych in-żynierskich zwyczajów, do jakich należy mię-dzy innymi stworzenie odpowiedniego mo-delu systemu, używanie wzorców projekto-wych czy testowanie oprogramowania, jest nieodzownym elementem tworzenia opro-gramowania wysokiej jakości. Co więcej, pro-gramowanie dla tak specyficznej platformy jak Java Card nie może być wymówką do za-przestania stosowania tych praktyk! Niestety, nawet przykłady kodu prezentowane dewe-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/;• Informacje dla deweloperów Java Card u producenta kart – Gemalto: http://developer.gemalto.com/home/java-card.html;• Wskazówki dla deweloperów Java Card/STK: http://developer.gemalto.com/fileadmin/

contrib/downloads/pdf/Java_Card_STK_Applet_Development_Guidelines.pdf ;• (U)SIM Application Programming Interface (API); (U)SIM API for Java Card: http://www.3gpp.org/ftp/Specs/html-info/31130.htm • USIM Application Toolkit (USAT): http://www.3gpp.org/ftp/Specs/html-info/31111.htm• Dokumenty Stepping Stones organizacji SIMalliance i inne materiały dla deweloperów:

http://www.simalliance.org/; • Repozytorium wzorców projektowych dla języka Java: http://www.javacamp.org/designPattern/;• Opis koncepcji list TLV: http://en.wikipedia.org/wiki/Type-length-value.

Listing 4. Test jednostkowy klasy ChangePINCommand

package pl.mobileexperts.sdj.samples;

import static org.easymock.EasyMock.*;

import static org.junit.Assert.*;

import javacard.framework.

import javacard.framework.*;

import org.junit.*;

public class ChangePinCommandTest {

private Command command;

@Before

public void setUp() throws Exception {

command = new ChangePinCommand();

assertNotNull(command);

}

@Test

public void testRun() {

byte oldPinLength = (byte) 3;

byte newPinLength = (byte) 4;

byte[] buffer = new byte[] { (byte) 1, (byte) 1, (byte) 1, (byte) 1,

newPinLength, (byte) 2, (byte) 2, (byte) 2, oldPinLength };

// record expected behaviour

Token token = createMock(Token.class);

expect(

token.verifyPin(buffer, (short) (newPinLength + 1),

oldPinLength)).andReturn(true);

token.changePin(buffer, (short) 0, newPinLength);

expectLastCall();

token.logout();

expectLastCall();

replay(token);

// run and verify

short result = command.run(token, buffer, (byte) buffer.length,

(short) 0);

assertEquals(result, (short) 0);

verify(token);

}

}

O AUTORACHLeszek Siwik, pracownik naukowy Katedry In-formatyki Akademii Górniczo - Hutniczej, oraz Krzysztof Lewandowski i Adam Woś - architek-ci i zarządzający m.in. projektem mobilnego PKI realizowanym w Mobile Experts sp. z o.o. Wszy-scy trzej specjalizują się w zagadnieniach szero-ko pojętego oprogramowania mobilnego, a w szczególności tematyką wykorzystania urządzeń mobilnych w płatnościach, bankowości oraz do zapewnienia bezpieczeństwa informacji.

Page 191: SDJ Extra 34 Biblia
Page 192: SDJ Extra 34 Biblia

192

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 193

Jesteś programistą C/C++, pasjonujesz się grafiką 3D, używałeś już OpenGL’a na desktopach i pragniesz rozpocząć swą

przygodę z programowaniem aplikacji mo-bilnych? A może nie miałeś wiele do czynie-nia z programowaniem OpenGL, ale z chęcią to nadrobisz, zaczynając na smart phone’ach? Czemu nie, mam nadzieję, że każdy znajdzie tu coś dla siebie. Choć artykuł ten nie jest peł-nym kompendium wiedzy na temat OpenGL ES, nie posłuży on nawet jako dokumentacja standardu, to zawiera kilka ciekawych, a mo-że nawet zaskakujących wskazówek związa-nych z tworzeniem aplikacji 3D na urządze-nia przenośne. Jeśli jesteś już doświadczo-nym programistą, obytym w branży mobil-nej, być może pracujesz nad wysokopozio-mowym silnikiem 3D, a może zastanawiasz się, jak zwiększyć wydajność swoich aplikacji, nie musisz czytać całej treści, przejrzyj ramki oznaczone nagłówkiem TIP i UWAGA.

W treści artykułu przyjęto kilka prostych konwencji. W celu skrócenia opisu niektó-rych funkcji w nawiasach klamrowych poda-ne są jej warianty dla różnych typów parame-trów wejściowych. I tak:

glColor4{f,x,ub}( T r, T b, T a );

oznacza, że funkcja jest zdefiniowana dla trzech typów parametrów (odpowiednio GLfloat, GLfixed, GLubyte). Wszystkie wystą-pienia T można zastąpić odpowiednim ty-pem parametru z klamerek (tak jakbyśmy definiowali template’y):

glColor4f( GLfloat r, GLfloat g, GLfloat b,

GLfloat a );

glColor4x( GLfixed r, GLfixed g, GLfixed b,

GLfixed a );

glColor4ub( GLubyte r, GLubyte g, GLubyte

b, GLubyte a );

Czytając artykuł, napotkacie również ramki oznaczone nagłówkiem TIP lub UWAGA; te pierwsze zawierają użyteczne informacje na temat efektywnego wykorzystania API, na-tomiast drugie zazwyczaj opisują problemy

związane z różnicami w implementacjach OpenGL ES na różnych platformach oraz sposoby ominięcia niektórych zagrożeń czy wąskich gardeł. Wszystko to ma w zamiarze autora stanowić zbiór użytecznych wskazó-wek na temat programowania mobilnych światów 3D, ale zacznijmy od początku.

Jak to się wszystko zaczęło, czyli droga od desktopów do mobilkówEwolucja grafiki 3D na urządzeniach mobil-nych rozpoczęła się bardzo niewinnie i bez zbytniego rozgłosu w 2001 roku. Gdzie? Oczywiście w Japonii. Pierwsze telefony z wbudowanym silnikiem 3D dostarczył ja-poński operator komórkowy J-Phone. Silnik graficzny dostarczony przez HI Corporation był wczesną wersją Mascot Capsule, obecnie jednego z bardziej rozpoznawanych wielo-systemowych graficznych API. Potrzeba było prawie roku, aby pierwsze tego typu urządze-nia ukazały się poza krajem wiśni. W 2002 odbyła się premiera Nokii 3410, która pomi-mo iż posiadała monochromatyczny wyświe-tlacz, umożliwiała użytkownikom tworzenie trójwymiarowych animacji tekstu, ściąganie animowanych wygaszaczy, a nawet zawiera-ła wbudowaną grę Java z prostą trójwymia-rową grafiką!

Potrzebę wzbogacenia przenośnych urzą-dzeń w zaawansowaną grafikę 3D dostrzegli również producenci middleware’u. Powstało w tym czasie wiele wysoko-poziomowych sil-ników graficznych, które nie tylko pozwalały na tworzenie trójwymiarowych scen, ale nie-rzadko udostępniały narzędzia do odtwarza-nia animacji, obsługi dźwięków i przechwyty-wania zdarzeń od użytkownika. ExEn (Execu-tion Engine), Swerve, X-Forge czy mophun to tylko niektóre z nich. Rozwiązania te, oparte na software’owej rasteryzacji, jakkolwiek bar-dzo elastyczne, nie pozwalały na dalszy roz-wój w kierunku szybszego przetwarzania gra-

Open GL ES 1.x

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

Dowiesz się:• Jak wyglądała ewolucja standardu OpenGL

ES i jakie były przesłanki jego utworzenia; • Jakie są ograniczenia związane z programo-

waniem aplikacji 3D na urządzeniach prze-nośnych;

• Jak wykorzystać GLES API do wydajnego ry-sowania efektownych scen 3D;

• Gdzie szukać potencjalnych wąskich gardeł aplikacji;

• Jakie są ograniczenia niektórych implemen-tacji i w jaki sposób można je obejść;

• Jak wygląda przyszłość grafiki 3D na urzą-dzeniach mobilnych.

Powinieneś wiedzieć:• Jak programować w języku C lub C++; • Czym jest OpenGL i jak jest skonstruowany

potok graficzny; • Przynajmniej trochę na temat programowa-

nia aplikacji OpenGL na desktopach.

Poziom trudności

Fakty i mity

Page 193: SDJ Extra 34 Biblia

192

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 193

fiki i polepszenia jej finalnej jakości . Korek-cja perspektywy, cieniowanie Gouraud’a czy blending pozostawały raczej tylko na sloga-nach reklamowych producentów (i sferze marzeń programistów), niż w finalnej aplika-cji – były po prostu zbyt kosztowne, aby zrzu-cać to na CPU. Ponadto większość pojawiają-cych się rozwiązań nie miała wystarczające-go pokrycia rynku, aby zapewnić rentowność bazujących na nich projektów, a także byt ich wydawcom.

Duża fragmentacja rynku była wynikiem walki o klienta i prześcigania się w nowin-kach przez producentów telefonów. Jednak sytuacja ta była bardzo niekorzystna dla deve-loperów aplikacji, którzy zostali zmuszeni do generowania tuzinów build’ów na setki urzą-dzeń, co skutecznie wydłuża czas develope-mentu, jak i zwiększa jego koszty.

Przemysł zareagował bardzo szybko, naj-ważniejsi gracze w tym biznesie zamiast wy-kańczać się w ciągłym wyścigu technologicz-nym, zauważyli potrzebę stworzenia stan-dardu – wspólnego API do tworzenia apli-kacji graficznych, które miałoby być punk-tem odniesienia również dla producentów urządzeń. Jako że standard miał być otwar-ty, za jego ojca wybrano OpenGL i już w ma-ju 2002 grupa Khronos ustanowiła sekcję ro-boczą (3d4W, 3Dlabs, ARM, ATI, Imagina-tion Technologies, Motorola, Nokia, Seawe-ed, SGI i Texas Instruments), której celem było zdefiniowanie nowej, odchudzonej wer-sji API dla urządzeń mobilnych. Rezultatem tego była lipcowa premiera OpenGL ES 1.0 (OpenGL for Embeded Systems) na konferencji SIGGRAPH 2003, a już rok później pojawiły się pierwsze urządzenia z GLES’em na pokła-dzie. Przy jego projektowaniu za najważniej-sze uznano następujące przesłanki:

• otwartość i niezależność od producen-tów;

• zapewnienie odpowiedniej warstwy abs-trakcji w celu ukrycia przed programi-stami specyfiki konkretnych rozwiązań natywnych oraz zagwarantowania wy-sokiej przenośności kodu - niezależności od platformy;

• minimalne zużycie zasobów systemo-wych, jak i ograniczona konsumpcja energii (urządzenia przenośne zasilane są z baterii);

• swobodę implementacji – od rozwiązań czysto software’owych po sprzętową ak-celerację;

• rozszerzalność i minimalna fragmenta-cja – ograniczenie opcjonalnych dodat-ków.

Kolejny SIGGRAPH 2004 zaowocował in-auguracją OpenGL ES w wersji 1.1, któ-rej celem była lepsza integracja z hardware-’em i wprowadzenie nowinek ułatwiających

Listing 1. Przykładowy kod do obsługi matematyki stałoprzecinkowej, wygenerowane z szablonów funkcje zwracają poprawne (ale niekoniecznie dokładne – w zależności od Q) rezultaty dla Q z zakresu <1, 31>. Dla przejrzystości implementacji z funkcji usunięto kod testujący przepełnienia (overflow) zmiennych// Types definitions

typedef signed char Int8;

typedef unsigned char UInt8;

typedef signed int Int32;

typedef long long Int64;

template < UInt8 Q >

class FixedTraits {

public:

static UInt8 const SHIFT = Q;

static GLfixed const ZERO = 0;

static GLfixed const ONE = ( 1 << Q );

static GLfixed const TWO = ( 2 << Q );

static GLfixed const HALF = ( 1 << (Q-1) );

static GLfixed const FRAC_BITS = (ONE - 1);

static GLfixed const INT_BITS = (~FRAC_BITS);

}; // class FixedTraits

template < UInt8 Q >

inline float Fixed2Float( GLfixed x)

{

return x / ( (float)( FixedTraits< Q >::ONE ) );

}

template < UInt8 Q >

inline int Fixed2Int( GLfixed x)

{

// Simple x >> Q - may be not portable

if (x >= 0) return (int)( x >> Q );

else return (int)( ~( (~x) >> Q ) );

}

template < UInt8 Q >

inline GLfixed Float2Fixed( float x)

{

return (GLfixed)(x * (float)( FixedTraits< Q >::ONE ) );

}

template < UInt8 Q >

inline GLfixed Int2Fixed( GLfixed x )

{

return ( ( (GLfixed)(x) ) << Q );

}

template < UInt8 Q >

inline GLfixed FixedMul( GLfixed val1, GLfixed val2 )

{

return (GLfixed)( ((Int64)val1 * (Int64)val2) >> Q );

}

template < UInt8 Q >

inline GLfixed FixedDiv( GLfixed val1, GLfixed val2 )

{

return (GLfixed)( ( ( (Int64)val1 ) << Q ) / val2 );

}

template < UInt8 Q >

inline GLfixed FixedSqrt( GLfixed val, UInt8 prec = Q / 2 )

{

register GLfixed s;

// At least one iteration is required

if( prec == 0 )

prec = 1;

s = ( val + FixedTraits< Q >::ONE ) >> 1;

for ( ; prec > 0; --prec )

s = ( s + FixedDiv< Q >(val, s) ) >> 1;

return s;

}

Page 194: SDJ Extra 34 Biblia

194

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 195

programowanie gier. Następnym krokiem było pojawienie się OpenGL ES 2.0 (ma-rzec 2007), bazowanego na desktopowym OpenGL 2.0. Wprowadza on możliwości programowania potoku graficznego na po-ziomie przetwarzania wierzchołków i frag-mentów (oświetlenie, materiały, texturing). Rezultatem tego jest zastąpienie wielu funk-cji kontrolujących potok graficzny imple-mentacją własnych na poziomie vertex i frag-ment shader’ów.

A jak jest dzisiaj?Obecnie OpenGL ES doczekał się wielu im-plementacji zarówno software’owych, jak i sprzętowych, dość wymienić kilka z nich:

• Rasteroid, upubliczniona biblioteka OpenGL ES 1.1 i OpenVG 1.0, kom-patybilna z desktopowymi okienkami i urządzeniami opartymi na Windows

Mobile i Symbian’ie (niestety producent Hybrid został wykupiony przez NVI-DIA i w praktyce biblioteka jest trudno dostępna);

• Vincent, implementacja Open Source OpenGL ES 1.1, przeznaczona głównie na Pocket PC 2003 i Microsoft Smart-phone 2003, doczekała się portów rów-nież na innych platformach;

• OpenGL ES 1.0 Linux, przykładowa im-plementacja oparta na OpenGL 1.3;

• PowerVR OpenGL ES, dostępny na urzą-dzeniach z akceleratorami Imagina-tion Technologies. W wersji 1.0 głównie sprzęt oparty na Windows Mobile Po-cket PC 5.0 i 2003. Natomiast w wersji 1.1 dostępny na telefonach koncernu So-ny Ericsson (M600, P990 W950, itd.), a także Motorola (Z8), Samsung (GT-i8510 i GT-i7110) i Nokia (N93/N95 oraz wiele innych). Ostatnio pojawił się

nawet chip ze wsparciem dla GLES 2.0 montowany fabrycznie w Samsungu Omnia HD;

• Biblioteki dostarczane wraz z SDK’a-mi przez producentów urządzeń: Qu-alcomm BREW OpenGL ES 1.0 Exten-sion, Nokia Series 60 (urządzenia z se-rii S60 2nd Ed FP 2 i wyżej posiadają co najmniej implementację software’ową, seria N, poczynając od pionierskiej N93, jest wyposażana w sprzętowy akcelera-tor grafiki), SonyEricsson Symbian UIQ 3 SDK;

• GLES’a na urządzeniach spod znaku nadgryzionego jabłka (Apple): IPod 5th Generation iPod, iPod Nano 3rd oraz iPod Classic, iPhone/iTouch;

• Pre-instalowane biblioteki na PDA i smartphone’ach.

Aktualną listę urządzeń ze wsparciem dla GLES’a można znaleźć na stronie GLBench-mark (patrz ramka: W sieci). Oprócz tego znajdziecie tam wyniki dokładnych testów wydajności, a także informacje o wspieranej wersji API i listę dostępnych rozszerzeń.

Trochę numerologiiZanim rozpoczniemy naszą wycieczkę w głąb standardu, warto powiedzieć kilka słów w te-macie wersjonowania OpenGL ES. Numery 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-patybilne wstecz, tak więc aplikacja wykorzy-stująca GLES 1.0 powinna się kompilować, linkować i uruchamiać na platformie 1.1. Nie będzie ona jednak użyteczna na urządze-niach z wersją 2.0 na pokładzie.

Ile z GL’a pozostało w GLES’ieOpenGL 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 redundantnych funkcji niektóre punkty spe-cyfikacji zostały diametralnie zmienione.

Float czy fixedChoć desktopowy GL jest oparty na arytme-tyce zmiennoprzecinkowej, większość urzą-dzeń mobilnych nie ma wsparcia do sprzęto-wej obsługi float’ów (brak FPU). Dlatego też dodano wsparcie dla nowego typu GLfixed, który zapisany jest na 32-bitowej wartości cał-kowitej. Pierwsze 16 bitów opisuje tu część całkowitą liczby ze znakiem, a pozostałe 16 wartość ułamkową - wielkość tę interpretu-je się jako x/2^16. Ilość bitów zarezerwowa-ną na część ułamkową określa się często jako Q, OpenGL ES używa formatu Q16. Listing 1 zawiera kilka użytecznych funkcji arytme-tyki stałoprzecinkowej dla dowolnego Q. Ze

TIP: Fixed pointW 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 funkcje służące przekazujące dane o wierzchołkach (glVertexPointer, itp). Użycie małego 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 GLfixed nie zawsze skutkuje oczekiwanym rezultatem. Jakkolwiek ma duże znaczenie w przy-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 mogą i tak dokonywać wewnętrznej konwersji liczb stałoprzecinkowych na float (zaintereso-wanych odsyłam do artykułu R.S.Wright’a OpenGL and Mobile Devices, patrz: Literatura)

UWAGA: Rozmiary punktówNie 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 wielkości - można ją odczytać przez glGetInteger(GL _ SMOOTH _ POINT _ SIZE _ RANGE). Z kolei GL _ ALIASED _ POINT _ SIZE _ RANGE zwraca zakres rozmiarów bez wygładzania. Niestety, jedynym wymaganym przez standard wymiarem jest 1.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 włączonym antialiasing’iem często sprowadza się do jednego piksela.

UWAGA: LinieRysowanie linii może być bardzo przydatne na etapie debugowania aplikacji: rysowanie przecięć, normalnych, promieni itp. Niestety niektóre (nawet komercyjne) implementacje GLES’a opierają się standardom i nie umożliwiają ich rysowania!

TIP: Paski trójkątówWielu 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 normalnej trójkąta, a zatem jego przednią ścianę. Niewidoczne trójkąty (odwrócone tylną ścianą do obserwatora) rysują, wyłączając lub zmieniając, mechanizm usuwania niewidocz-nych ścianek:

glDisable(GL_CULL_FACE); // Disable back-face culling

glCullFace(GL_FRONT); // Enable culling of front faces

Należy wystrzegać się takich skrótów, gdyż prowadzą one do szybkiego spadku wydajności aplikacji.

Page 195: SDJ Extra 34 Biblia

194

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 195

standardu usunięto również wszystkie funk-cje obsługujące typ zmiennoprzecinkowy o podwójnej precyzji (GLdouble) , zastępując je wariantami na float’ach. Natomiast dla każ-dej funkcji przyjmującej GLfloat dodano wa-riant obsługujący GLfixed. Dane wierzchoł-ków są przesyłane do potoku tylko za pomo-cą typów: GLbyte, GLubyte, GLshort, GLfloat lub GLfixed.

Brak trybu immediateWersja mobilna GL’a nie pozwala na natych-miastowe (bezpośrednie) przesyłanie wierz-chołków za pomocą komend glBegin, glEnd. Wywołania te bardzo komplikowałyby imple-mentację maszyny stanów OpenGL, a defi-niowanie geometrii z ich użyciem jest dale-kie od optymalności i w rzeczywistości nie zalecane nawet w zastosowaniach desktopo-wych. Stąd też dane wierzchołków są przesy-łane w postaci tablic (vertex arrays) za pośred-nictwem glDrawElements czy glDrawArrays, zrezygnowano również z display lists.

Stosy macierzyGLES 1.0 wprowadza mniej restrykcyjne wy-magania co do wielkości stosów macierzy:

• wymaganą głębokość stosu modelu-wi-doku (ang. model-view) zmniejszono z 32 do 16;

• pozostałe stosy (transformacji współ-rzędnych tekstury i macierzy projekcji) muszą mieścić przynajmniej 2 elementy.

TeksturyOpenGL ES nie pozwala na odczyt po-przednio załadowanej tekstury (brak glGetTexImage). Multi-texturing aczkolwiek pozostał w standardzie, to wymagania co do ilości jednostek teksturowania ograniczono początkowo (GLES 1.0) do jednej (sic!), a na-stępnie dwóch (GLES 1.1). Pozostawiono je-dynie wsparcie dla tekstur 2D, gdyż jedno-wymiarowe odpowiedniki są zbyt proste do emulacji, aby doczekały się osobnej imple-mentacji. Natomiast tekstury 3D uznano za zbyt kosztowne obliczeniowo.

Tylko pięć najważniejszych formatów tek-stury pozostało w standardzie, wprowadzono wsparcie dla formatów z paletą oraz możli-wość obsługi formatów natywnych dla danej platformy. Generowanie koordynatów tek-stury zostało usunięte ze standardu, gdyż ist-nieje możliwość jego programowej emulacji.

Kolory i oświetlenieSpecyfikację kolorów wierzchołków ograni-czono do RGBA, indeksowanie kolorów zo-stało całkowicie usunięte (choć żadna to stra-ta). Poza tym wprowadzono kilka mniej istot-nych zmian ograniczających możliwości defi-niowania materiałów – więcej szczegółów w dalszej części opracowania.

Przetwarzanie fragmentówTen etap potoku graficznego pozostał pra-wie nienaruszony, aczkolwiek bufor szablo-nu (stencil) zdefiniowano jedynie opcjonal-nie. Operacje GL_INCR_WRAP i GL_DECR_WRAP zostały całkowicie usunięte.

Operacje na buforachZe względów na ograniczoną wydajność i roz-miary docelowej biblioteki na zaszło tu naj-więcej zmian:

• rendering 2D (glDrawPixels, glBitmap) został całkowicie wycięty ze standardu (może być łatwo emulowany przez ma-powanie dwóch trójkątów);

• bufory głębokości i szablonu nie mogą zostać odczytane (usunięto glReadBuffer i glCopyPixels);

• zrezygnowano z bufora akumulacji;• odczyt bufora ramki (glReadPixels)

jest wciąż możliwy w dwóch forma-tach pikseli: GL _ RGBA i natywnym GL _

IMPLEMENTATION _ COLOR _ READ _

FORMAT _ OES, za to bardzo niewskazany (i w rzeczywistości nie zawsze dostępny).

Nowości w ES 1.1Jak już wspomniano, odświeżona wersja GLES 1.1 została przygotowana z myślą o wydajnym wykorzystaniu akceleratorów graficznych. Poczyniono też ukłon w stronę programistów gier i dodano kilka użytecz-nych funkcji. Spośród kilku nowości warto wymienić:

• wsparcie dla VBO (Vertex Buffer Object), to jest buforów do przechowywania da-nych wierzchołków po stronie serwera (odczyt tych buforów nie jest możliwy, co pozwala na optymalizację rozkładu

danych w pamięci i konwersję do natyw-nych formatów);

• rozszerzenie (niestety opcjonalne) do wydajnego rysowania grafiki 2D glDrawTexOES – rysowanie odbywa się tu przez jednostkę teksturowania, więc bitmapy mogą być przechowywane po stronie serwera;

• point sprites to nowy sposób na rende-rowanie billboardów 2D i efektów czą-steczkowych, więcej na ten temat w dal-szej części opracowania;

• mechanizm generowania mip-map (GL _ GENERATE _ MIPMAP);

• rozszerzenie minimalnej wymaganej licz-by jednostek teksturowania do dwóch;

• texture combiners, czyli nowe możliwości przetwarzania tekstur, operacje na wy-branych rejestrach wejściowych (kanał alfa, składowe kolorów) oparte na zesta-wie pre-definiowanych instrukcji (włą-czając mnożenie skalarne wektorów, przydatne przy implementacji bump mapping’u);

• dynamic state queries, śledzenie zmian stanów maszyny GL w trakcie działania aplikacji, dzięki czemu nie jest koniecz-ne ich zapisywanie po stronie aplikacji – glIsEnabled pozwala na łatwe spraw-dzenie, czy testowany stan jest aktywny.

ES w praktyce

Trochę prymitywnych informacjiRozpoczynając to krótkie wprowadzenie po tajnikach OpenGL ES, trudno nie wspo-mnieć o dostępnych prymitywach graficz-nych. Prymitywy w tym kontekście ozna-czają najprostsze obiekty graficzne (geome-tryczne), które służą do budowania całej sce-ny. Od wersji OpenGL ES 1.0 wspiera ryso-

Listing 2. Przygotowanie do rysowania point sprites

// Setup point sprites rendering

glPointSize( 5 );

glEnable( GL_TEXTURE_2D );

glActiveTexture( GL_TEXTURE0 );

glBindTexture( GL_TEXTURE_2D, textureHandle );

glEnable( GL_POINT_SPRITE_OES );

glTexEnvi( GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_TRUE );

// Draw all points

glDrawArrays( GL_POINTS, offset, size );

Tabela 1. Liczba składowych (per vertex) oraz typy danych wspierane przez mechanizm vertex arrays (GLES 1.0/1.1)

Vertex array Sizes Types

glVertexPointer 2,3,4 GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT

glTexCoordPointer 2,3,4 GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT

glNormalPointer 3 GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT

glColorPointer 4 GL_UNSIGNED_BYTE, GL_FIXED, GL_FLOAT

glPointSizePointerOES 1 GL_FIXED, GL_FLOAT

Page 196: SDJ Extra 34 Biblia

196

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 197

wanie punktów, linii i trójkątów, natomiast wersja 1.1 rozszerza ten zestaw o tak zwane point sprites.

W kolejnych akapitach znajdziecie krótki opis dostępnych prymitywów. Programiści, którzy mają już spore doświadczenia z wersją desktopową GL’a, mogą po prostu pominąć tę część, warto jednak rzucić okiem na cieka-wostki umieszczone w ramkach.

PunktyPunkt jako najprostszy prymityw wymaga określenia tylko jednego wierzchołka, acz-kolwiek pamiętajmy, że punkt to nie to sa-mo, co piksel ekranowy, gdyż oprócz po-łożenia punkt może mieć określoną wiel-kość:

void glPointSize{f,x}( T size );

Wartość ta będzie obowiązywać do następ-nego wywołania funkcji, a domyślnie wyno-si 1. Rysowanie punktów może odbywać się w dwóch trybach: z wygładzaniem (tzw. an-tialiasing) lub bez.

glEnable( GL_POINT_SMOOTH );

glDisable( GL_POINT_SMOOTH );

W pierwszym przypadku punkty rysowa-ne są jako koła, a piksele brzegowe na ekra-nie są półprzezroczyste w zależności od stop-nia ich pokrycia przez rysowany punkt. W sy-tuacji braku wygładzania punkt reprezento-wany jest na ekranie przez kwadrat o wielko-ści zaokrąglonej do najbliższej wartości całko-witej. Gdy zaokrąglenie sprowadza się do ze-ra, punkty reprezentowane są w postaci pik-sela (wielkość 1).

LinieDo zdefiniowania najprostszej linii potrzebu-jemy co najmniej dwóch wierzchołków, wiąże się z tym kilka sposobów na ich rysowanie:

• GL _ LINES (segmenty), rysowane są przez połączenie każdej pary dwóch wierzchołków. Oznacza to, iż do na-rysowania dwóch segmentów musimy przesłać 4 wierzchołki (odpowiednio 1, 2 pierwszy segment i 2, 3 na drugi). Nie

jest to najlepsze rozwiązanie do rysowa-nia kilku połączonych segmentów;

• GL _ LINE _ STRIP (pasek linii), każdy ko-lejny wierzchołek na liście jest łączony li-nią z poprzednim;

• GL _ LINE _ LOOP, podobny do poprzed-niego, z tym że pierwszy i ostatni wierz-chołek z listy są dodatkowo łączone.

Podobnie jak punkty, linie posiadają szerokość:

void glLineWidth{fx}(type width);

i mogą być wygładzane przez wykorzystanie antialiasing ’u:

glEnable(GL_LINE_SMOOTH);

TrójkątyW mobilnej wersji GL’a trójkąty są jedyny-mi prymitywami, które pozwalają na ryso-wanie wypełnionych siatek. Czworokąty (GL_QUADS) i wielokąty (GL_POLYGONS) obec-ne co prawda w wersjach desktopowych zo-stały pominięte w celu odchudzenia imple-mentacji. Istnieją trzy sposoby definiowania geometrii na podstawie trójkątów:

• GL _ TRIANGLES, każdy trójkąt przesyła-ny jest do potoku niezależnie – definiu-ją go zawsze 3 wierzchołki (2 trójkąty = 6 wierzchołków). Jest to najmniej wydaj-na metoda, która pozwala na definiowa-nie dowolnych siatek, a nawet luźno roz-rzuconych trójkątów. Niestety, jeśli trój-kąty współdzielą ze sobą wierzchołki (są połączone w logiczną całość), mamy do czynienia z redundancją danych;

• 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-finiują pierwszy trójkąt, a każdy następ-ny wierzchołek generuje nowy trójkąt złożony z tego wierzchołka i dwóch po-przednich. Metoda ta jest dużo szybsza od GL _ TRIANGLES, wymaga jednak or-ganizacji listy wierzchołków i zwrócenia szczególnej uwagi na kolejność ich zawi-jania;

• 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-wy trójkąt przez dodanie poprzedniego i pierwszego wierzchołka z listy.

Billboardy, systemy cząstek, czyli krótka saga o point sprites i ich wielkościWykorzystanie tak zwanych billboardów po-zwala na uzyskanie ciekawych efektów gra-ficznych, a w szczególności efektów cząstecz-

UWAGA: Point spritesPrzycinanie 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-leżnie od jego wielkości). Ułatwia to wydajną implementację na poziomie API, jednak stwa-rza problemy programistom aplikacji – cząsteczki znikają zbyt wcześnie! Rozwiązaniem te-go problemu jest ustawienie widoku (glViewport) na rozmiar większy od wielkości ekranu (o połowę wielkości największego sprite’a) oraz ustawienie testu nożyc (scissors test) do roz-miarów ekranu:

glViewport( -PS_SIZE/2, -PS_SIZE/2, dispW+PS_SIZE/2, dispH+PS_SIZE/2 );

glEnable( GL_SCISSOR_TEST );

glScissor( 0, 0, dispW, dispH );

Niestety większość dostępnych implementacji ma problemy z przycinaniem sprite’ów w sytu-acji rozszerzonego widoku lub z rysowaniem point sprites wogóle.

TIP: Systemy cząstek a point spritesPomimo 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 punktów z każdorazowym ustawieniem domyślnej wielkości (glPointSize) jest również pewnym rozwiązaniem. Oczywiście ciągłe wysyłanie tak małych porcji danych stworzy nie-potrzebny ruch na szynie, stawiając pod znakiem zapytania wydajność tej metody. Oczywi-ście pod GLES 1.0, który nie wspiera teksturowania punktów, implementacja systemów czą-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 ramka: W sieci) lub w przypadku GLES 1.1 skorzystanie z rozszerzenia glDrawTexOES (o nim w dalszej części artykułu).

TIP: Vertex arraysPrzechowywanie 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-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-piowane z pamięci klienta przed narysowaniem geometrii. Choć można by to uznać za istot-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-wany przez tablice nie może zostać zwolniony dopóki są one wykorzystywane do renderin-gu sceny.

Page 197: SDJ Extra 34 Biblia

196

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 197

kowych (ang. particle effects). Umożliwia to symulację wielu zjawisk występujących we wszechświecie: dym, obłoki, ogień, śnieg, deszcz, iskry, rozbryzgi wody itp. (Rysunek 2 przedstawia kilka przykładów zastosowań systemów cząstek). W niektórych przypad-kach dwuwymiarowe billboardy mogą za-stąpić nawet rzeczywistą geometrię (drzewa, krzaki, trawa, patrz Rysunek 3). Począwszy od GLES 1.1 obiekty tego typu mogą być ren-derowane za pomocą tzw. point sprites, czyli teksturowanych punktów o określonej wiel-kości zawsze zwróconych w stronę kamery. Oczywiście używanie ich do symulowania drzew sprawdzi się tylko wtedy, gdy kame-ra porusza się jedynie w dwóch wymiarach (np. po powierzchni terenu), w innym przy-padku, gdy obserwator unosi się nad billbo-ardami, powstaje bardzo nieprzyjemny efekt kładzenia się obiektów. Do tego typu symula-cji stosuje się billboardy sferyczne, które obra-cają się tylko w jednej osi i konieczna jest ich własna implementacja (patrz ramka: W sieci). Rozszerzenie point sprites ma w zamyśle zastą-pić mało wydajne tworzenie billboardów na bazie trójkątów. Czy jest ono faktycznie tak pomocne, osądzicie sami, ale najpierw kilka słów o interfejsie.

Aby rozpocząć rysowanie sprite’ów, na-leży aktywować odpowiedni stan, używa-jąc tokena GL_POINT_SPRITE_OES, urucho-mić teksturowanie i ustawić środowisko dla aktywnej jednostki teksturowania na GL_COORD_REPLACE_OES (przykład – Listing 2).

Od tej pory wszystkie punkty przesyła-ne do serwera graficznego będą rysowane w formie teksturowanych billboardów. Ich wielkość może być określona odgórnie dla całej tablicy przesyłanej do serwera (przez wywołanie glPointSize, opisane w sekcji 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-przód w kontekście tworzenia systemów czą-steczkowych. Począwszy od GLES 1.1 istnie-ją dwa sposoby modyfikowania rozmiarów punktów w obrębie jednej tablicy: points at-tenuation i points size arrays.

Points attenuation, czyli wygaszanie wielko-ści wraz z odległością od obserwatora, odby-wa się z użyciem formuły:

att_size = base_size * sqrt(1 / (a + b*d +

c*d*d));

gdzie d jest odległością od obserwatora w przestrzeni widoku. Pozostałe współczynni-ki równania ustalamy za pomocą funkcji:

void glPointParameter{fx}v(

GL_POINT_DISTANCE_ATTENUATION,

const T* coeffTab);

przekazując do niej tablicę {a, b, c} (para-metr coefTabb). W praktyce ustalenie war-tości współczynników na {1, 0, 0} (usta-wienie domyślne) wyłącza wygaszanie.

Points size arrays, czyli tablice wielkości punktów, umożliwiają całkowicie dowolne precyzowanie rozmiarów sprite’ów. Pozwa-la to na implementację bardziej subtelnych efektów cząsteczkowych. Tablica wielkości punktów jest zapisywana po stronie klienta poprzez wywołanie glPointSizePointerOES, aby aktywować jej użycie, należy dodać glEnableClientState(GL_POINT_SIZE_

ARRAY_OES) – więcej na ten temat w części poświęconej vertex arrays. Tablice rozmiarów mogą być używane jednocześnie z ich wy-gaszaniem (points attenuation, patrz wyżej), wielkość zapisana w tablicy stanowi wtedy bazę do pomniejszania. Oznacza to, że mana-ger cząstek może obsługiwać ich skalowanie w czasie (na przykład powiększające się obłoki dymu), podczas gdy zmniejszaniem sprite’ów zgodnie z odległością od obserwatora (skrót perspektywiczny) zajmie się OpenGL.

Wyjściowy rozmiar punktu (już po przekształceniach GL_POINT_DISTANCE_

ATTENUATION) jest przycinany do zakresu

użytkownika, zakres ten można ustalić, uży-wając funkcji:

void glPointParameter{fx}(

GL_POINT_SIZE_MIN, T param);

void glPointParameter{fx}(

GL_POINT_SIZE_MAX, T param);

Na koniec wielkość sprite’ów (czy też punk-tów) może być jeszcze modyfikowana przez bibliotekę, jeśli wykracza ona poza rozmia-ry wspierane przez implementację (warto więc sprawdzić, jakie są dozwolone rozmia-ry punktów na platformie docelowej). Ilu-struje to poniższy pseudokod:

screen_size = implementation_clamp (

user_clamp ( att_size ) );

Przekazywanie geometrii do potoku renderującego

Vertex arrays, jak wycisnąć soki z GLES 1.0OpenGL ES 1.0 definiuje tylko jeden sposób przesyłania geometrii do potoku renderują-cego, wszystkie atrybuty wierzchołków (po-zycja, kolor, koordynaty tekstury itp.) mu-

TIP: Wykorzystanie wartości domyślnychWartości domyślne, choć z pozoru proste rozwiązanie, otwierają olbrzymie pole do optyma-lizacji. Nie chodzi tu jedynie o zmarnowane linie kodu i redundancje danych. Jeśli jakikolwiek z atrybutów (normalna, kolor, koordynaty tekstury) nie ulega zmianie, nie musimy zapychać szyny przesyłaniem tych samych wartości. Co więcej, sterownik graficzny może efektywniej wykorzystywać zawartość rejestrów i pamięci podręcznej.

TIP: Indeksowanie wierzchołkówUż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-rownik graficzny, najlepiej przekazać mu dane w jak najbardziej przystępnej postaci. Najlep-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 sposób, aby powtarzające się indeksy wierzchołków sąsiadowały ze sobą na liście, a kolejne trójkąty wykorzystywały jak najwięcej wspólnych danych. Należy przy tym pamiętać o kolej-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-chołków. Optymalizacje te powinniśmy wprowadzać już na etapie eksportowania geometrii do odpowiednich formatów, a nie podczas ładowania aplikacji.

TIP: Wydajność tablic wierzchołków a formaty danychFormat danych przechowywanych w tablicach wierzchołków może mieć duży wpływ na wydajność aplikacji. Użycie pamięciożernych (a więc i dokładniejszych) typów powoduje większe obciążenie zasobów systemowych zwłaszcza przy przesyłaniu geometrii do GPU, może być też powodem mało efektywnego wykorzystania rejestrów procesora i cache mis-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-mentacji sprzętowych oparta jest na w pełni zmiennoprzecinkowym potoku, aby ograni-czyć transfer danych, warto i tu zastanowić się nad naszymi potrzebami. Używanie GL _FLOAT do przekazywania kolorów wierzchołków jest sporym nadużyciem – w zupełności wystarczy GL _ UNSIGNED _ BYTE . Jeśli to tylko jest możliwe, warto zamienić float na by-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-nych do natywnego formatu.

Page 198: SDJ Extra 34 Biblia

198

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 199

szą być przekazane w formie tablic. Mecha-nizm ten (tzw. vertex arrays) definiuje prosty zestaw funkcji, który pozwala na ustawienie wskaźników do tablic atrybutów przechowy-wanych po stronie klienta.

Funkcje do obsługi vertex arrays mają po-dobną strukturę dla każdego typu atrybu-tów, różnice objawiają się w obsługiwanych formatach danych:

void glColorPointer(GLint size,

GLenum type, GLsizei stride,

GLvoid* pointer);

void glTexCoordPointer(GLint size,

GLenum type, GLsizei stride,

GLvoid* pointer);

void glVertexPointer(GLint size,

GLenum type, GLsizei stride,

GLvoid* pointer);

void glNormalPointer(GLenum type,

GLsizei stride, GLvoid* pointer);

// Starting from ES 1.1 (optional

extension)

void glPointSizePointerOES( GLenum type,

Lsizei stride, GLvoid* pointer);

Parametr size określa tu wymiarowość da-nych, dla tablicy współrzędnych wierzchoł-ków (glVertexPointer) może to być 2 lub 3 – pozycja jest określana przez odpowiednio

dwa {x,y} lub trzy {x,y,z} koordynaty. Tabli-ce normalnych i wielkości punktów nie po-siadają tego parametru, gdyż normalne za-wsze określamy w trzech wymiarach, a roz-miar punktu jest wielkością skalarną (jed-nowymiarową). Parametr stride określa odległość (w bajtach) pomiędzy poszcze-gólnymi elementami tablicy. Pozwala to na używanie jednego bloku pamięci do okre-ślania wielu atrybutów wierzchołków, na przykład w formacie {pozycja, normalna, pozycja normalna, ...}. Wartość stride jest używana do adresowania kolejnych ele-mentów określonego atrybutu (normalna, kolor itp.) w obrębie spakowanej tablicy. Stride równy zero oznacza, że przekazuje-my wskaźnik do tablicy jednego atrybutu, kolejne wystąpienia tego atrybutu w tablicy nie są rozdzielone przez inne dane. Z kolei gdy chcemy użyć upakowanej tablicy kilku atrybutów, podajemy tu wartości większe od zera. Przykładowo użycie jednej tabli-cy do określenia pozycji i normalnych zde-finiowanych trzema składowymi typu GL _

SHORT wymaga przekazania stride równe-go 6 (3 * sizeof(GL _ SHORT)). Parametr type określa format przechowywanych da-nych (GL _ BYTE, GL _ SHORT, etc.), patrz: Tabela 1. Natomiast pointer to wskaźnik do obszaru pamięci zawierającego właściwe

dane wierzchołków. Pamiętajmy, iż wymie-niona tu funkcja glPointSizePointerOES została wprowadzona jako opcjonalne roz-szerzenie dopiero w wersji 1.1 standardu, więcej na jej temat w sekcji poświęconej ry-sowaniu point sprites.

Wykorzystanie upakowanych tablic wierz-chołków (stride większy od zera) choć nie prowadzi do mniejszego zużycia pamięci, może skutkować wzrostem wydajności apli-kacji. Ułożenie wszystkich atrybutów jedne-go wierzchołka w spójnym obszarze pamięci pozwala sterownikowi na efektywniejsze za-rządzanie pamięcią (cache-coherency).

Aby konkretna tablica atrybutów zosta-ła wykorzystana w potoku graficznym, nale-ży uruchomić odpowiedni stan klienta, uży-wając funkcji:

void glEnableClientState(GLenum cap);

analogicznie możemy (i powinniśmy) wyłą-czyć ją z przetwarzania (po wykonaniu ryso-wania) poprzez:

void glDisableClientState(GLenum cap);

Do obu funkcji przekazujemy typ tabli-cy: GL_COLOR_ARRAY, GL_NORMAL_ARRAY, GL_

TEXTURE_COORD_ARRAY, GL_VERTEX_ARRAY, GL_

POINT_SIZE_ARRAY_OES. Domyślnie wszystkie tablice są wyłączone.

Dodatkowego wyjaśnienia wymaga uży-cie tablicy koordynatów tekstury, ponieważ OpenGL ES wspiera multi-texturing, przed ich ustawieniem musimy poinformować maszynę, która jednostka teksturowania jest używana, dotyczy to wywołań:

glEnableClientState(GL_TEXTURE_COORD_ARRAY),

glDisableClientState(GL_TEXTURE_COORD_ARRAY)

oraz

glTexCoordPointer.

Służy do tego:

void glClientActiveTexture(GLenum texture);

gdzie texture definiuje aktywowaną jed-nostkę teksturowania (GL _ TEXTURE0,

GL _ TEXTURE1, aż do GL _ MAX _ TEXTURE _

UNITS). Pamiętajmy, że GLES 1.0 gwarantu-je wsparcie tylko dla jednej jednostki tekstu-rowania, a wersja 1.1 dla dwóch, reszta jak zwykle zależy od implementacji.

Wartości domyślneOprócz przekazywania danych geometrii przez tablice OpenGL ES (już od wersji 1.0) umożliwia ustawienie wartości domyślnych dla niektórych atrybutów (normalnych, ko-loru i koordynatów tekstury):

UWAGA: VBO różnice w implementacjachChoć 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 sytuacją, gdzie bufory nie mogą być rozszerzane. Oznacza to, iż jeśli raz zostanie dla nich za-alokowany pewien obszar pamięci, nie będzie można przekazać nowego bloku o większej objętości. Obejście tego problemu może wymagać implementacji własnej puli buforów i za-rządzania nimi na poziomie aplikacji.

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-nik poinformowany o zamiarze częstego odświeżania bufora nie powinien dokonywać kosz-towych optymalizacji reprezentacji danych. Jakkolwiek dane są wciąż przechowywane po 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ć 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 koordynatów tekstury, podczas gdy pozycje wierzchołków są animowane.

TIP: Wydajny update VBO 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ę-ci dla bieżącego VBO, podczas gdy glBufferData spowoduje zwolnienie całej pamięci i za-alokowanie jej na nowo. Niektóre sterowniki mogłyby wykryć sytuacje, w których bufor jest odświeżany z tym samym zestawem parametrów (ta sama wielkość i sposób użycia), ale nie polegaj na ich implementacji.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-nego nadpisywania bufora w jednej ramce. Nadpisanie danych bufora, które są wciąż uży-wane w potoku renderowania, powoduje wymuszenie synchronizacji i wstrzymanie wyko-nywania do momentu przetworzenia przez silnik graficzny nadpisywanego obszaru.

Page 199: SDJ Extra 34 Biblia

198

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 199

void glNormal3{fx}(T nx, T ny, T nz);

void glColor4{fx ub}(T red, T green,

T blue, T alpha);

void glMultiTexCoord4{fx}(GLenum target,

T s, T t, T r, T q);

Jeśli któraś z tablic nie została aktywowana przez wywołanie glEnableClientState , zo-staną użyte wartości domyślne, dzięki cze-mu nie musimy za każdym razem definio-wać tego samego koloru wierzchołków czy przekazywać tę samą normalną na płaskiej powierzchni (patrz ramka: Wykorzystanie wartości domyślnych).

Rendering prymitywów z wykorzystaniem vertex arraysPo ustawieniu wskaźników na tablice wierz-chołków można przystąpić do właściwego ry-sowania prymitywów. Za pomocą funkcji:

void glDrawArrays(GLenum mode, GLint first,

GLsizei count);

przekazujemy wszystkie tablice do potoku renderującego, oczywiście wcześniej musi-my zdefiniować co najmniej tablicę pozy-cji wierzchołków. Jeśli któryś z atrybutów nie ma zdefiniowanej tablicy, zostaną uży-te wartości domyślne. Do rysowania zosta-nie użytych count elementów, zaczynając od elementu o indeksie first. Z kolei wartość mode determinuje sposób rysowania prymi-tywów (temat ten poruszony został w sek-cji poświęconej prymitywom, patrz : Rysu-nek 1): GL_POINTS, GL_LINES, GL_LINE_LOOP,

GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_

STRIP, GL_TRIANGLE_FAN.

Należy zwrócić uwagę, iż wywołanie glDrawArrays wymaga odpowiedniego uło-żenia wierzchołków w tablicy, zgodnie z wybranym sposobem definiowania prymi-tywów, i może prowadzić do dużej redun-dancji danych. Dotyczy to w szczególno-ści: GL_LINES, GL_TRIANGLES. Dlatego nale-ży używać tej metody głównie do rysowania punktów, pasków i wachlarzy trójkątów (GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN) lub li-nii. W pozostałych przypadkach dużo efek-tywniejsze jest wykorzystanie indeksowania wierzchołków za pomocą funkcji:

void glDrawElements(GLenum mode, GLsize

count, GLenum type, const GLvoid* idx);

gdzie mode odpowiada za typ rysowanych prymitywów, count określa liczbę indek-sów do przetwarzania, type definiuje typ indeksu (GL _ UNSIGNED _ BYTE lub GL _

UNSIGNED _ SHORT), a idx jest po prostu wskaźnikiem do tablicy indeksów. Choć me-toda ta używa dodatkowej tablicy do adreso-wania tablic wierzchołków, jeśli dany wierz-chołek jest współdzielony przez kilka trójką-

tów, powielany jest tylko jego indeks, a nie wszystkie atrybuty (więcej w ramce: Indek-sowanie wierzchołków).

Vertex buffer objects, ukłon w stronę hardware’uOpenGL ES 1.1 w ramach lepszej integra-cji z hardware’m wprowadza mechanizm pozwalający na przechowywanie danych wierzchołków po stronie serwera rende-ringu. Mechanizm ten, zwany vertex buf-fer objects, eliminuje konieczność ciągłego przesyłania danych geometrii od klienta do serwera w każdej ramce aplikacji (jak ma to miejsce przy użyciu vertex arrays w wersji 1.0). Ponadto pozwala sterownikom na we-wnętrzną konwersję danych do typów bar-dziej przyjaznych dla hardware’u, a także na lepszy rozkład ich w pamięci w celu wydaj-nej adresacji.

Obsługa VBO jest dość prosta i zaczyna się od utworzenia uchwytów (nazw) do obiek-tów bufforów:

void glGenBuffers( GLsizei n,

GLuint* bufferNames );

Wywołanie tej funkcji powoduje utworzenie n nazw i zapisanie ich w tablicy przekazanej wskaźnikiem bufferNames (oczywiście w ge-stii użytkownika jest, aby tablica ta miała od-powiedni rozmiar). Nazwy te są od tej pory rozpoznawalne przez maszynę stanów i zazna-czone jako używane. Wywołanie to nie tworzy jeszcze żadnych buforów, więc muszą one zo-stać wygenerowane i aktywowane za pomocą:

void glBindBuffer(GLenum target,

GLuint bufferName);

gdzie do target przekazujemy token GL _

ARRAY _ BUFFER (bufor tablicy wierzchołków – do obsługi vertex arrays) lub GL _ ELEMENT _

ARRAY _ BUFFER (bufor do przechowywania indeksów), a bufferName jest uchwytem (na-zwą) obiektu bufora. Funkcja ta tworzy nowy bufor pod daną nazwą lub wiąże już istnieją-cy z wybranym celem (target). Jeżeli przeka-zany identyfikator bufora jest większy od ze-ra i nie jest skojarzony z żadnym VBO, two-rzony jest nowy bufor o zerowej wielkości. W przypadku gdy buferName wskazuje na ist-niejący już obiekt, silnik ustawia go jako bie-

Listing 3. Wykorzystanie VBO do renderowania geometrii

// Assuming that we have two tables defined

// GLshort verts[] = { … };

// GLbyte normals[] = { … };

// Create buffer handles

GLuint vboHandles[2];

glGenBuffers( 2, vboHandles );

// Load some vertex data into the first VBO

glBindBuffer( GL_ARRAY_BUFFER, vboHandles[0] );

glBufferData( GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW );

// Load vertices’ normals into the second VBO

glBindBuffer( GL_ARRAY_BUFFER, vboHandles[1] );

glBufferData( GL_ARRAY_BUFFER, sizeof(normals), normals, GL_STATIC_DRAW);

glEnableClientState( GL_VERTEX_ARRAY );

glEnableClientState( GL_NORMAL_ARRAY );

// Setup attribute arrays pointers

glBindBuffer( GL_ARRAY_BUFFER, vboHandles[0] );

glVertexPointer( 2, GL_SHORT, 0, NULL );

glBindBuffer( GL_ARRAY_BUFFER, vboHandles[1] );

glNormalPointer( GL_BYTE, 0, NULL );

// Setup global color attribute

glColor4x( 1 << 16, 0, 0, 1 << 16 );

// Ignore first 2 vertices, draw 2 triangles

glDrawArrays( GL_TRIANGLE_FAN, 2, 4 );

// Unbind VBO

glBindBuffer( GL_ARRAY_BUFFER, 0 );

Page 200: SDJ Extra 34 Biblia

200

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 201

żący dla danego celu (bez tworzenia nowe-go VBO). W innym przypadku, gdy wartość uchwytu wynosi zero, maszyna odpina bieżą-cy bufor związany z podanym celem. Innymi słowy wartość zero jest zarezerwowana, nie odpowiada ona żadnemu domyślnemu bu-forowi, a jedynie służy do zaznaczenia, iż nie zamierzamy używać VBO do obsługi dane-go celu i wracamy do użycia pamięci klien-ta (czyli w rzeczywistości vertex arrays, ale o tym później).

Usuwanie istniejących już buforów i zwal-nianie ich zasobów odbywa się przez wywo-łanie funkcji:

void glDeleteBuffers(GLsizei n,

const GLuint* bufferNames);

Funkcja ta zwalnia n buforów o identyfikato-rach z tablicy bufferNames. Oznacza to, że za-alokowana dla nich pamięć po stronie serwera zostaje zwolniona, a nazwy zwrócone do po-nownego użycia (lista nazw, czyli obsługiwa-nych buforów, jest ograniczona). Jeżeli który-kolwiek ze zwalnianych buforów był używany

do określania atrybutów wierzchołków (o tym za chwilę), skojarzenie to zostanie zerwane. Po utworzeniu nazw i właściwych buforów można przystąpić do wypełnienia ich danymi. Funkcja:

void glBufferData(GLenum target,

GLsizeiptr size, const GLvoid* data,

GLenum usage);

służy do wypełnienia aktywnego bufora sko-jarzonego z celem target danymi o wielko-ści size bajtów, zaczynając od adresu data. Oznacza to alokację obszaru pamięci po stronie serwera i skopiowanie przekazanych danych. Jeśli aktualny bufor zawierał już ja-kieś dane, są one automatycznie zwolnione i nowy obszar powinien zostać zaalokowany (sprawdź ramkę: VBO różnice w implementa-cjach). W praktyce po wyjściu z tej funkcji pamięć aplikacji (pod adresem data) może zostać zwolniona, oczywiście jeśli nie chce-my jej już wykorzystywać – np. modyfiko-wać danych wierzchołków.

Osobnego wyjaśnienia wymaga para-metr usage, który może przyjmować wartości

GL_STATIC_DRAW lub GL_DYNAMIC_DRAW. W pierwszym przypadku informuje on sterownik, iż dane przekazane do bufora nie będą zmienia-ne (nadpisywane), co umożliwia ich wewnętrz-ną optymalizację (lepsze rozłożenie w pamięci itp.). Z kolei GL_DYNAMIC_DRAW oznacza, że za-wartość bufora może ulegać częstej modyfika-cji – przy każdym odświeżeniu ramki, a nawet podczas jednego przebiegu renderingu (prze-czytaj tip: Czy zawsze warto używać VBO?).

GLES 1.1 pozwala również na nadpisanie tylko części danych bieżącego VBO, służy do tego funkcja:

void glBufferSubData(GLenum target,

GLintptr offset, GLsizeiptr size,

const GLvoid* data);

Większość parametrów tej funkcji jest tożsa-ma glBufferData, na uwagę zasługuję brak parametru usage, gdyż nie możemy zmie-nić sposobu użycia zaalokowanego już bufo-ra (patrz ramka: Wydajny update VBO). Do-datkowo pojawia się tu offset, który wyzna-cza odległość w bajtach (w obszarze bufora) do początku bloku, który zamierzamy nad-pisać. Wywołanie to oczywiście nie pozwala na rozszerzenie rozmiaru bufora, dlatego je-śli offset + size, przekracza jego wielkość, generowany jest błąd GL _ INVALID _ VALUE, a dane nie są w ogóle kopiowane.

Rysowanie prymitywów z użyciem VBOPo zainicjalizowaniu obiektu bufora i zapisaniu do niego danych można go wykorzystać do defi-niowania dowolnych atrybutów wierzchołków (patrz sekcja o vertex arrays). Podpięcie bufora do tablicy wierzchołków dokonuje się za pomo-cą funkcji z rodziny vertex array pointer (glVer-texPointer, glTexCoordPointer). Zmienia się natomiast znaczenie ostatniego parame-tru tych funkcji. Nie jest on interpretowany ja-ko wskaźnik na dane wierzchołków w obszarze pamięci klienta, lecz oznacza on offset w obsza-rze aktualnie podpiętego bufora, od którego bę-dą przekazywane dane. Innymi słowy określa on ilość pomijanych danych w obszarze bufora przy odczycie odpowiedniego atrybutu. Ozna-cza to, że wywołanie:

void glNormalPointer(GLint size,

GLenum type, GLsizei stride,

GLvoid* pointer);

w GLES 1.1 może mieć różne znaczenie, w zależności od tego, czy w danej chwili do GL _ ARRAY _ BUFFER jest podpięty jakiś VBO. Standardowe działanie tych funkcji przywraca wypięcie aktywnego bufora tabli-cy glBindBuffer(GL _ ARRAY _ BUFFER, 0). Dlatego dobrą praktyką jest wyłączanie bu-forów zaraz po ich użyciu.

Podobnie jak w przypadku standardo-wych vertex arrays, wykorzystanie parame-

Rysunek 2. Przykłady zastosowania systemów cząstek opartych na billboardach. Źródło: opracowanie własne

Rysunek 1. Rodzaje prymitywów i kolejność definiowania ich wierzchołków. Źródło: K.Pulli, T.Aarnio, V.Miettinen, K.Roimela, J.Vaarala; Mobile 3D Graphics with OpenGL ES and M3G

Page 201: SDJ Extra 34 Biblia

200

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 201

tru stride pozwala na pakowanie danych o wierzchołkach do jednego bufora, dzięki cze-mu można wykorzystać jeden bufor do opisu kilku atrybutów.

Po związaniu buforów z odpowiednimi ta-blicami atrybutów można przystąpić do wła-ściwego rysowania prymitywów. Odbywa się to dokładnie tak samo jak w przypadku zwy-kłych tablic, za pomocą funkcji:

void glDrawArrays(GLenum mode, GLint first,

GLsizei count);

Listing 3 powinien nieco bardziej rozjaśnić meandry zastosowania vertex buffer objects.

VBO i indeksowane tablice wierzchołkówObiekty buforów mogą być również używane do indeksowania wierzchołków (a właściwie tablic wszystkich atrybutów). Służą do tego specjalne typy VBO – bufory indeksów (GL_ELEMENT_ARRAY_BUFFER). Zarządzanie bufo-rem indeksów odbywa się w analogiczny spo-sób jak obsługa bufora tablicy. Jedyna różnica polega na tym, iż jako cel zastosowania bufora (target) zamiast GL_ARRAY_BUFFER podajemy GL_ELEMENT_ARRAY_BUFFER w funkcjach do je-go obsługi: glBindBuffer, glBufferData, and glBufferSubData. Po przygotowaniu takiego VBO można go wykorzystać do renderowania geometrii przez wywołanie glDrawElements (patrz sekcja dotycząca vertex arrays). Jed-nakże i tu zmienia się nieco interpretacja pa-rametrów tej funkcji. Jeżeli do jednostki GL_ELEMENT_ARRAY_BUFFER jest przypięty obiekt bufora, to ostatni parametr glDrawElements wyznacza offset w obszarze bufora, od którego ma się rozpocząć indeksowanie prymitywów:

void glDrawElements(GLenum mode,

GLsize count, GLenum type,

const GLvoid*offset);

Aby przywrócić standardową obsługę tej funkcji, należy odpiąć aktywny bufor in-deksacji:

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

Rysowanie prymitywów z użyciem glDrawElements i bufora indeksów pozwa-la na wydajne indeksowanie wszystkich ak-tywnych tablic atrybutów (co najmniej GL _

VERTEX _ ARRAY musi być ustawiona, w in-nym przypadku prymitywy nie zostaną skonstruowane). Oczywiście najlepsze efek-ty osiągniemy, gdy do obsługi tych tablic zo-staną wykorzystane również obiekty bufo-rów. Listing 4 prezentuje prosty przykład in-deksowania wierzchołków z użyciem VBO. Prezentowany kod rysuje cztery trójkąty, których dane załadowano do bufora wierz-chołków i koordynatów tekstury.

Mapowanie teksturZe względu na wciąż ograniczone możliwo-ści konfiguracji potoku renderującego mapo-wanie tekstur odgrywa znaczącą rolę w GLES 1.x. Koncepcyjnie wiele się tu nie zmieniło w porównaniu z jego desktopowym odpowied-nikiem, jednak kilka detali wymaga krótkiego omówienia, gdyż mogą być przyczyną wielu nieprzespanych nocy podczas przesiadki z de-sktopów. Zacznijmy jednak od początku.

Mapowanie tekstur odbywa się na etapie ra-steryzacji trójkątów, aczkolwiek niektóre im-plementacje odwlekają ten proces na koniec po-toku, aby uniknąć niepotrzebnej obróbki frag-mentów odrzuconych przez test bufora głębo-kości czy nożyc. Zarządzanie teksturami przy-pomina obsługę VBO, bitmapy są przechowy-wane w obiektach tekstur, do których odwołu-je się, używając ich nazw (uchwytów). Prosty ze-staw funkcji pozwala na tworzenie, aktywowa-nie (podpinanie) i usuwanie obiektów tekstury:

void glGenTextures( GLsizei n,

GLuint* textureNames );

void glBindTexture( GL_TEXTURE_2D,

GLuint textureName );

void glDeleteTextures( GLsizei n,

const GLuint* texturesNames );

Funkcje te działają tak samo jak ich deskto-powe odpowiedniki, więc nie warto poświę-cać im więcej uwagi (programistów, którzy

stawiają swe pierwsze kroki z GL’em, odwo-łuję do red book’a – patrz Literatura: OpenGL SuperBible). Warto jednak przypomnieć, że GLES 1.x nie ma wsparcia dla tekstur jedno- i trójwymiarowych, dlatego też podaję tu sta-łą GL _ TEXTURE _ 2D do parametru celu w glBindTexture, dotyczy to wszystkich funkcji, w których podajemy wymiarowość tekstury.

W GLES dane tekstury są przechowy-wane po stronie serwera (znów analogia do VBO), co oznacza , że za każdym razem, gdy zapisujemy bitmapę do obiektu tekstury, dokonuje się ich kopiowanie. Jest to operacja potencjalnie czasochłonna, gdyż większość implementacji dokonuje na tym etapie kon-wersji obrazu do natywnego formatu sprzy-jającemu szybkiemu mapowaniu.

Jakby tego było mało, samo kopiowa-nie danych do serwera jest już wąskim gar-dłem, dlatego należy poczynić starania, aby raz załadowaną mapę wykorzystywać jak najdłużej – rozwiązaniem mogą być atlasy tekstur. Funkcja:

void glTexImage2D( GL_TEXTURE_2D,

GLint level, GLenum internalformat,

GLsizei width, GLsizei height,

GLint border, GLenum format,

GLenum type, const GLvoid* pixels );

kopiuje dane bitmapy z pamięci aplikacji do obiektu tekstury po stronie serwera. Funkcja

Rysunek 3. OpenGL ES w akcji, screeny z prac nad TigerWoods09 iPod. Iluzoryczny efekt oświetlenia uzyskano, obliczając składową diffuse per vertex na etapie ładowania geometrii i przekazując ją do koloru wierzchołków. Ilu billboardów użyto w tych scenach? Źródło: opracowanie własne

TIP: Atlasy teksturAtlasy tekstur są powszechnie używaną metodą pakowania kilku obrazów w jedną większą mapę. Oszczędności z nimi związane nie wynikają jedynie z bardziej efektywnego wykorzy-stania pamięci tekstury, minimalizacja wielkości niewykorzystanych obszarów tekstury zwią-zanych z ograniczeniem POT. Zyskujemy również na przełączaniu między mapami, a nawet na szybszym dostępie do ich danych, gdyż nowoczesne układy graficzne mogą przetrzymy-wać cały atlas w pamięci podręcznej.

Tabela 2. Formaty tekstury i wspierane dla nich formaty pikseli

Format tekstury Format piksela

GL_LUMINANCE GL_UNSIGNED_BYTE

GL_ALPHA GL_UNSIGNED_BYTE

GL_LUMINANCE_ALPHA GL_UNSIGNED_BYTE

GL_RGB GL_UNSIGNED_BYTEGL_UNSIGNED_SHORT_5_6_5

GL_RGBA GL_UNSIGNED_BYTEGL_UNSIGNED_SHORT_4_4_4_4GL_UNSIGNED_SHORT_5_5_5_1

Page 202: SDJ Extra 34 Biblia

202

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 203

ta, choć pochodzi z wersji desktopowej GL’a, ma jednak sporo ograniczeń. Mobilny GL nie wspiera obramówek tekstury, stąd też border może przyjmować tylko wartość zero. War-

tości parametrów internalformat i format muszą być zawsze takie same (brak konwersji formatów tekstury), natomiast type, czyli for-mat pikseli, został mocno okrojony (patrz: Ta-

bela 2). Szerokość i wysokość tekstury muszą zawsze być potęgą dwójki (power of two – w skrócie POT). W GLES nie istnieje bezpośred-ni sposób na stworzenie tekstury z obrazu, któ-ry nie spełnia tego warunku (brak wsparcia dla GL _ PACK _ ROW _ LENGTH i GL _ PACK _ ROW _

LENGTH). Można jednak obejść to ogranicze-nie, kopiując dane bitmapy do większego bufo-ra o rozmiarach POT, a następnie ładując go do obiektu tekstury. Sposób ten ma jednak istot-ną wadę: obraz o rozmiarach 129x257 będzie reprezentowany przez teksturę 256x512, czy-li prawie czterokrotnie większą, co się wiąże z transferem dużej porcji danych i mocno nad-miarowym zużyciem pamięci. Inną meto-dą jest logiczny podział bitmapy na mniejsze fragmenty o rozmiarach POT, obszary te ko-piujemy do tymczasowego bufora, a następ-nie do obiektu tekstury za pomocą wywołania glTexSubImage2D. Jest szansa, że oszczędzimy nieco na transferze danych. Jednak w obu wa-riantach dość szybko może się okazać, iż nie dysponujemy odpowiednią ilością pamięci vi-deo do załadowania dużych obiektów tekstu-ry. Najlepszym rozwiązaniem jest upakowywa-nie kilku obrazów do jednej tekstury w ten spo-sób, aby maksymalnie wykorzystać zaalokowa-ny dla niej obszar. Można to robić już na etapie eksportu obrazów do specjalnie przygotowane-go formatu. Format ten nazywany jest często atlasem tekstur (patrz ramka: Atlasy tekstur).

Domyślnie rozmiar wiersza bitmapy prze-kazywanej do funkcji glTexImage2D musi być wielokrotnością słowa (word), co w prak-tyce oznacza, iż musi zawierać parzystą wie-lokrotność 4 bajtów. Zachowanie to można zmienić, używając funkcji:

void glPixelStorei( GL_UNPACK_ALIGNMENT,

GLint param );

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-wych). Pozostałe dozwolone wartości to: 2 (wyrównanie do 2 bajtów), 4 (słowo), 8 (wy-równanie do 8 bajtów).

Jeszcze więcej różnicWarto jeszcze wspomnieć o kilku ogranicze-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 przenoszenia kodu z desktopów lub pisania wieloplatformowej aplikacji:

• zdarza się, że implementacja nie pozwa-la na realokację i rozszerzanie obiektu tekstury, raz zaalokowana przestrzeń dla danej nazwy nie może zostać zwol-niona bez resetowania GL’a. Oznacza

Listing 4. Przykład użycia VBO do indeksowania wierzchołków

// Assuming that vboHandles[0] is a handle to vertex positions buffer,

// vboHandles[1] points to texture coordinates vbo.

// indexTable forms a list of indexes to vertex attributes defined

// in mentioned buffers

// GLubyte indexTable[] = { 0, 1, 2, … };

// Use VBO for vertex positions

glEnableClientState( GL_VERTEX_ARRAY );

glBindBuffer( GL_ARRAY_BUFFER, vboHandles[0] );

glVertexPointer( 2, GL_BYTE, 0, NULL );

// Set client-side active texture unit

glClientActiveTexture( GL_TEXTURE0 );

// Use VBO for texture coordinates definition

glEnableClientState( GL_TEXTURE_COORD_ARRAY );

glBindBuffer( GL_ARRAY_BUFFER, vboHandles [1] );

glTexCoordPointer( 2, GL_BYTE, 0, NULL );

// Load vertex indices into newly created VBO

GLuint vboIndHandle;

glGenBuffers( 1, &vboIndHandle );

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vboIndHandle );

glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(indexTable), &indexTable, GL_STATIC_

DRAW );

// Bind VBO used for indexing

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vboIndHandle );

// Draw four triangles using 12 indexes

glDrawElements( GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, NULL );

// Unbind all buffer objects

glBindBuffer( GL_ARRAY_BUFFER, 0 );

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

UWAGA: 16-bitowe formaty teksturyPrzy ł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 _SHORT _ x) będą odczytywane przez bibliotekę, używając natywnego wskaźnika na 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-wanego przez narzędzia eksportujące obrazy. W takim przypadku należy zamienić miejsca-mi każde dwa bajty w ładowanej bitmapie przed uploade’m do obiektu tekstury.

UWAGA: Definiowanie materiałów subtelne różniceGLES 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 glMaterial.Składowe materiałów inne niż ambient i diffuse w wersji mobilnej GL’a nie mogą być definio-wane per vertex, choć było to możliwe w wersji desktopowej (brak możliwości lokalnej kon-troli rozbłysków). Jednak wiele efektów oświetleniowych może być symulowanych przez użycie multi-texturing’u. W mobilnej wersji pominięto również mechanizm indeksowania ko-lorów.

Page 203: SDJ Extra 34 Biblia

202

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 203

to, że kolejne upload’y bitmapy pod da-ną nazwą tekstury muszą mieć rozmiar mniejszy lub równy pierwszej alokacji;

• specyfikacja OpenGL ES nie nakłada restrykcji co do dokładności interpola-cji koordynatów tekstury, przez co nie-które implementacje mogą wykonywać tylko szybką interpolację w przestrze-ni ekranu, pomijając w ten sposób znie-kształcenie perspektywy (perspective cor-rection). Jeśli efekty są rażące, można sto-sować większą taselację trójkątów (Rysu-nek 4);

• potencjalnie kosztowna interpolacja mip-map może być niedostępna, a zastę-puje ją wybór najbliższej mip-map'y.

Rozszerzenie Draw TexturePomimo wielu ograniczeń, dowodem na to, iż konsorcjum standaryzacyjne OpenGL ES kierowało się przede wszystkim wydajnością biblioteki, jest wprowadzone w wersji 1.1 rozszerzenie glDrawTexOES. Występująca w kilku wariantach funkcja:

void glDrawTex{sifx}OES(T x, T y, T z,

T width, T height);

void glDrawTex{sifx}vOES(const T * coords);

wykorzystuje najszybszy dla danej platformy sposób rysowania dwuwymiarowych prosto-kątów. W wersjach software’owych może to być nawet bezpośrednie blit’owanie do bufo-ra ramki. Natomiast implementacje sprzęto-we korzystają raczej z pasków trójkątów. Roz-szerzenie to jest szczególnie użyteczne do ry-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 głębokość rysowanego prostokąta, co pozwala na łatwe układanie kilku warstw GUI.

Kolory, materiały, oświetlenieKolorowanie wierzchołków w GLES niewie-le się różni od desktopowego pierwowzoru. Jak już wspomniano przy omawianiu tablic wierzchołków i VBO, definicję kolorów dla poszczególnych wierzchołków przesyła się za pomocą funkcji:

void glColor4{fx ub}( T red, T green, T

blue, T alpha );

void glColorPointer( GLint size,

GLenum type, GLsizei stride,

GLvoid* pointer );

Mając na uwadze, iż pierwsza z nich usta-wia domyślny kolor prymitywów, które nie mają zdefiniowanej tablicy kolorów, a druga funkcja definiuje kolory dla każdego wierz-

chołka. W rezultacie kolory wierzchołków implikują finalną barwę fragmentów po-wstałych z danego prymitywu (oczywiście w sytuacji wyłączonego oświetlenia). Gene-rowanie fragmentów może przyjąć tu dwie formy, w zależności od aktywnego trybu cieniowania:

void glShadeModel(GLenum mode);

W przypadku trybu GL _ FLAT, wszystkie fragmenty powstałe z rysowanego prymity-wu przyjmują kolor jego ostatniego wierz-chołka. Z kolei tryb GL _ SMOOTH (cieniowa-nie Gouraud’a) pozwala na interpolację ko-loru fragmentów (również koloru będące-go wypadkową oświetlenia i użytych ma-teriałów), dzięki czemu uzyskujemy płynne przejścia koloru między wierzchołkami.

Prosty model cieniowania, z użyciem jedy-nie kolorów, nie sprawdza się, jeśli dążymy do osiągnięcia bardziej subtelnych efektów odwzorowujących oświetlenie. Włączenie oświetlenia, glEnable(GL_LIGHTING), impli-kuje konieczność określenia materiałów dla rysowanych geometrii. Funkcja:

void glMaterial{fx}v( GL_FRONT_AND_BACK,

GLenum pname, const T* params );

definiuje składowe materiału, które bę-dą użyte w równaniu oświetlenia. Paramet pname określa nazwę składowej materiału (po więcej informacji odwołuję do OpenGL SuperBible, patrz Literatura): GL _ AMBIENT,

GL _ DIFFUSE, GL _ AMBIENT _ AND _

DIFFUSE, GL _ SPECULAR, GL _ EMISSION. Natomiast params to wskaźnik na tablicę koloru tej składowej (RGBA). Inna wersja tej funkcji pozwala ustawić intensywność rozbłysku (shininess):

void glMaterial{fx}( GL_FRONT_AND_BACK,

GL_SHININESS, T param );

gdzie param przyjmuje wartości z zakre-su 0-128.

Oprócz ustalenia materiału dla całej ryso-wanej geometrii, GLES pozwala także na de-finiowanie składowych ambient i diffuse dla każdego wierzchołka z osobna. Wymaga to aktywacji stanu:

glEnable( GL_COLOR_MATERIAL );

Od tej pory kolory wierzchołków przekazy-wane przez tablicę kolorów (glColorPoin-ter) będą używane do określenia tych skła-dowych (zobacz też ramkę: Definiowanie ma-teriałów subtelne różnice).

Model oświetlenia odziedziczony z wer-sji OpenGL 1.3 pozostał praktycznie nie-zmieniony, również tutaj mamy możli-wość ustawiania świateł punktowych, kierunkowych i stożkowych. Wywołanie funkcji:

glLight{x,f}v(GLenum light, GL_POSITION,

const T* pos);

ustawia pozycję światła we współrzęd-nych homogenicznych (cztery składowe x,

Rysunek 5. Programowalny potok graficzny w OpenGL ES 2.0. Źródło: A.Munshi, D.Ginsburg, D.Shreiner; OpenGL ES 2.0 Programming Guide

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

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

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

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

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

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

���

Rysunek 4. Taselacja trójkątów

Page 204: SDJ Extra 34 Biblia

204

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 205

y, z, w). O typie światła decyduje tu czwar-ta składowa podawana w tablicy pozycji, jeżeli jest ona równa zero, światło inter-pretowane jest jako kierunkowe (umiesz-czone nieskończenie daleko w kierun-ku określonym przez x, y, z), a w równa-niu oświetlenia nie uwzględnia się współ-czynników wygaszania (GL _ CONSTANT _

ATTENUATION, GL _ LINEAR _ ATTENUATION,

GL _ QUADRATIC _ ATTENUATION). Świa-tło punktowe definiuje się, podając w = 1, pozycja światła jest wtedy uwzględnia-na przy obliczaniu oświetlenia i obliczane jest wygaszanie (zobacz też ramkę: Pozycja światła – względna czy bezwzględna). Stoż-kowe źródła światła definiuje się, ustawia-jąc parametry GL _ SPOT _ DIRECTION i

GL _ SPOT _ CUTOFF, tak więc nic się tutaj nie zmieniło w stosunku do desktopów. Przykład pozycjonowania świateł w scenie przedstawia Listing 5.

Należy wspomnieć, że GLES udostępnia również globalny model światła otaczającego oraz możliwość oświetlania przednich i tyl-nych ścianek trójkątów:

void glLightModel{fx}v( GL_LIGHT_MODEL_

AMBIENT, const T*

rgba )

void glLightModel{fx}( GL_LIGHT_MODEL_TWO_

SIDE, T(1) )

Gdy zdecydujemy się na użycie modelu oświetlenia obu stron trójkątów, do oblicza-nia ich koloru jest używana ta sama normal-na (nie możemy zdefiniować osobnych nor-malnych dla przednich i tylnych ścian trój-kąta). Różnica polega na tym, iż w przypad-ku ścianek odwróconych tyłem do obser-watora normalne są odwracane (przeciwny zwrot) zanim zostaną uwzględnione w rów-naniu oświetlenia.

Przyszłość OpenGL ES – kierunki rozwojuOstatnie lata zaowocowały błyskawicznym postępem w sferze urządzeń przenośnych, większość nowych smart phone’ów wyposa-żona została w specjalizowane układy gra-ficzne. Grupa Khronos, przewidując roz-wój sytuacji rynkowej, ze sporym wyprze-

dzeniem rozpoczęła prace ukierunkowa-ne na lepszą integrację API z akcelerato-rami grafiki. Efektem tego było pojawie-nie się w roku 2007 nowej wersji standar-du – OpenGL ES 2.0. Najnowsza wersja GL’a czerpie swe korzenie z desktopowego odpowiednika OpenGL 2.0 i jest ukierun-kowana tylko na urządzenia wyposażone w programowalne układy graficzne (telefo-ny, PDA, instrumenty lotnicze, nawigacyj-ne itp.). GLES 2.0 wprowadza świeże spoj-rzenie na potok graficzny, w którym eta-py przetwarzania wierzchołków i kolorów fragmentów są definiowane (programowa-ne) przez programistę aplikacji (patrz: Ry-sunek 5). W związku z tą zmianą, standard składa się z dwóch uzupełniających się spe-cyfikacji:

• OpenGL ES 2.0 API – biblioteka do obsługi standardowego potoku graficz-nego;

• OpenGL ES Shading Language (OpenGL ES SL) – specyfikacja języka programowania potoku (vertex shaders, fragment shaders).

Choć nowa specyfikacja dziedziczy wie-le cech po swoim desktopowym pierwo-wzorze, również i tu nie zapomniano o ograniczeniach platformy docelowej. Aby uszczuplić wielkość binariów mobilnej bi-blioteki, zrezygnowano z redundantnych funkcji, ograniczając ich zestaw do najbar-dziej pożądanego minimum pozwalające-go na osiągnięcie tych samych rezultatów (wciąż nie znajdziemy tu display lists czy trybu immediate). Dla osiągnięcia maksy-malnej wydajności shader’ ów i redukcji po-boru mocy (zużycia baterii) wprowadzono kilka trybów precyzji. Nareszcie lepsza in-tegracja z hardware’em pozwala na progra-mowanie efektów zarezerwowanych do-tychczas tylko dla dużych platform. Na-leży spodziewać się, że w przyszłości bę-dzie pojawiać się coraz więcej urządzeń ze wsparciem dla GLES 2.0, które już te-raz można spotkać w sprzedaży (Samsung Omnia HD).

Wprowadzenie programowalnego po-toku graficznego otwiera nowe możliwo-ści tworzenia realistycznych efektów gra-ficznych, a kompilacja shader’ów w trakcie wykonywania programu pozwala na dużą elastyczność i zwiększa przenośność kodu. Niestety nie wszystko wygląda tak różowo jakby się zdawało. Kompilacja programów 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-pilowane (na desktopie) binaria. Może to doprowadzić do sytuacji, w której przeno-śna aplikacja (czy wieloplatformowy fra-mework) będzie musiała obsługiwać wiele

Listing 5. Pozycjonowanie różnych typów świateł w scenie

#define Int2Fx( a ) ( ( a ) << 16 )

GLfixed lightPos0[] = { 0, 0, 0, Int2Fx(1) };

GLfixed lightPos1[] = { 0, 0, Int2Fx(1), 0 };

GLfixed lightPos2[] = { Int2Fx(2), 0, 0, Int2Fx(1) };

glMatrixMode( GL_MODELVIEW );

glLoadIdentity();

// Light0 is directional light shining towards -Z (negative) camera axis.

glEnable( GL_LIGHT0 );

glLightxv(GL_LIGHT0, GL_POSITION, lightPos0 );

// Light1 - point light always located in camera position

glEnable( GL_LIGHT1 );

glLightxv(GL_LIGHT1, GL_POSITION, lightPos1 );

// Camera transformations

glRotatex( Int2Fx(30), 0, Int2Fx(1), 0 );

glTranslatex( 0, 0, -Int2Fx(20) );

// Setup point light in word coordinates

glEnable( GL_LIGHT3 );

glLightxv(GL_LIGHT3, GL_POSITION, lightPos2 );

// Render entire scene here

// DrawScene();

TIP: Pozycja światła – względna czy bezwzględnaNależ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 3D. Jeżeli w trakcie ustawiania jego pozycji macierz model-view jest macierzą jednostko-wą, światło będzie zawsze podążać za obserwatorem. Listing 5 prezentuje kilka sposobów lokalizacji oświetlenia.

Page 205: SDJ Extra 34 Biblia

204

Rozwiązania mobilne

SDJ Extra 34 Biblia

OpenGL ES 1.x

www.sdjournal.org 205

wersji pre-kompilowanych shader’ów. Ze względu na wydajność niektóre implemen-tacje GLES 2.0 będą wspierały tylko formy binarne. Kompilacja off-line będzie wyma-gała od developerów obsługi wielu narzę-dzi od różnych producentów (MakeBina-ryShader, BinaryShader.lib dostarczane przez AMD czy PVRUniSCo od Imagina-tion Technologies).

Niestety, kompilacja shader’ów nie zosta-nie jedynym problemem stojącym przed programistami aplikacji mobilnych. Rynek ten jest mocno sfragmentowany i do ko-mercyjnego sukcesu potrzeba wsparcia dla wielu OS’ów i różnych środowisk (BREW, Symbian, Windows Mobile, Linux itd.). Brak wspólnego API czy warstwy abstrak-cji, która przykrywa zarządzanie zasoba-mi systemowymi, jest istotną barierą wej-ścia. Choć GLES pozwala na zunifikowa-nie obsługi potoku graficznego, to zadania takie jak:

• dostęp do pliku;• obsługa urządzenia dźwiękowego;• instrukcje wejścia/wyjścia (obsługa kon-

trolera);• dostęp do zegara systemowego;• obsługa zdarzeń i przerwań.

są wciąż domeną systemów operacyj-nych. Pisanie własnej warstwy abstrakcji

może być sporym wyzwaniem, nie tylko dla początkujących programistów. Choć podjęto już próby rozwiązania proble-mów przenośności i kompatybilności ko-du (OpenC na Symbian'ie, Lightblue pod BREW), wciąż brakuje uniwersalnego API. Na szczęście również w tej materii grupa Khronos podjęła się próby wypro-wadzenia standardu. W lutym 2008 roku opublikowano specyfikację OpenKODE 1.0, która wprowadza zestaw API (włą-czając w to OpenGL ES i EGL’a) do komu-nikacji z docelowym systemem. KODE Core przykrywa większość funkcji zwią-zanych z alokacją pamięci, dostępem do plików i sieci czy obsługą zdarzeń. Pozo-staje tylko poczekać, jak do nowych stan-dardów ustosunkują się producenci sprzę-tu i OS’ów.

Reasumując, w niedalekiej przyszłości bę-dziemy świadkami dążenia do unifikacji roz-wiązań i naturalnego zbliżenia mobilnych urządzeń do ich desktopowych odpowiedni-ków. Odczucia wizualne, dźwiękowe, szyb-kość przetwarzania i możliwości aplikacji mobilnych przypominają sytuację na rynku desktopów z przed kilku lat, ale już teraz do-czekaliśmy się przenośnych wersji niektórych wielkich tytułów. Prowadzone są nawet pra-ce nad standaryzacją formatów wymiany con-tent’u graficznego, czego dowodem może być COLLADA.

Podsumowanie W tekście artykułu zostały przedstawione kluczowe aspekty wykorzystania OpenGL ES w programowaniu aplikacji mobilnych. Niestety, opisanie całego standardu wyma-gałoby napisania sporej wielkości książki, dlatego ilość podstawowych informacji sta-rałem się ograniczyć do niezbędnego mini-mum (sekcja dotycząca prymitywów). Ze względu na dużą dostępność materiałów na temat desktopowego GL’a starałem się tu ra-czej uchwycić tylko najważniejsze różnice pomiędzy OpenGL ES 1.x a jego desktopo-wym pierwowzorem, kładąc szczególny na-cisk na problemy związane z wydajnością. Pomimo wielu różnic pomiędzy tymi stan-dardami, znalazło się też miejsce na wyja-śnienie kilku wspólnych funkcji, których za-stosowanie może przysporzyć pewnych kło-potów początkującym programistom GL’a (vertex arrays, VBO, oświetlenie). Niektó-re z nich pojawiły się tu także ze wzglę-du na rozbieżności w ich implementacji na różnych platformach. Na koniec opracowa-nia przedstawiłem krótki zarys OpenGL ES 2.0, a także innych przyszłościowych stan-dardów, które mogą zmienić oblicze mobil-nych aplikacji.

Mam głęboką nadzieję, iż powyższe opra-cowanie posłuży Czytelnikom jako krótki zbiór porad i wskazówek, pomocnych przy pisaniu własnych aplikacji, a chciałbym je de-dykować zmarłej, w okresie mej pracy nad ar-tykułem, babci Anieli.

Choć programowanie grafiki na urządze-niach przenośnych stawia wiele wyzwań na-wet przed doświadczonym programistą, wi-dok własnej aplikacji 3D na niewielkim ekra-nie malutkiego komputera szybko zrekompen-suje Waszą pracę.

KRYSTIAN KOSTECKIPracuje 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-lacje fizyczne i efekty specjalne na potrzeby gier 3D. Pasjonuje się wdrażaniem rozwiązań mate-matycznych do symulacji zachowań, kontroli animacji i rozwiązywania problemów algoryt-micznych. Grupa BLStream powstała, by efek-tywniej wykorzystywać potencjał dwóch szyb-ko rozwijających się producentów oprogramo-wania – BLStream i Gamelion. Firmy wchodzą-ce w skład grupy specjalizują się w wytwarza-niu oprogramowania dla klientów korporacyj-nych, w rozwiązaniach mobilnych oraz produk-cji i testowaniu gier. Kontakt z autorem: [email protected]

W Sieci

• http://www.khronos.org/developers/resources/opengles/ – dostęp do wielu implementacji OpenGL ES, przykładowe programy, tutoriale;

• http://www.vincent3d.com/ – strona domowa open source’owej biblioteki GLES; • http://glutes.sourceforge.net/ – strona domowa mobilnej wersji znanej biblioteki GLUT; • http://www.zeuscmd.com/tutorials/opengles/index.php – kilkadziesiąt tutoriali dla stawia-

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,

a także testy wydajności, specyfikacja dostępnych rozszerzeń i wersji renderer'a.

Literatura

• D.Astle, D.Durnil, OpenGL ES Game Development. Course Technology, 2004;• K.Pulli, T.Aarnio, V.Miettinen, K.Roimela, J.Vaarala, Mobile 3D Graphics with OpenGL ES

and M3G. Morgan Kaufmann, 2007;• A.Munshi, D.Ginsburg, D.Shreiner, OpenGL ES 2.0 Programming Guide. Addison-Wesley,

2008;• D.Shreiner, J.Neider; OpenGL Programming Guide, The Official Guide to Learning OpenGL.

Addison-Wesley 2007;• Khronos, OpenGL ES Native Platform Graphics Interface (Version 1.0). Khronos Group,

2003;• Richard S.Wright, OpenGL and Mobile Devices. Dr. Dobbs Journal 2006/05;• Richard S.Wright, OpenGL and Mobile Devices: Round 2. Dr. Dobbs Journal 2008/07;• S. Zerbst, O. Duvel, 3D Game Engine Programming. Course Technology, 2004;• Richard S.Wright, OpenGL Super Bible, 4th Edition. Addison-Wesley, 2007;• K. Hawkins, D.Astle, A. LaMothe, OpenGL Game Programming. Course Technology, 2002.

Page 206: SDJ Extra 34 Biblia

206

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 207

Flash Lite to wersja popularnego odtwa-rzacza animacji internetowych dostoso-wanego do możliwości platformy mobil-

nej. Początki komórkowej wersji sięgają 2004 ro-ku, kiedy to jeszcze firma Macromedia ekspery-mentowała z prototypowymi rozwiązaniami te-go typu. Po przejęciu tej organizacji przez Ado-be Systems kontynuowano pracę nad Flash Li-te począwszy od wersji 1.0 wdrożonej w czerw-cu 2005 roku w japońskiej sieci NTT Docomo, gdzie do tej pory jest to jedna z dominujących technologii na tym rynku. W pozostałych re-gionach najbardziej popularne wersje tego od-twarzacza to 1.1, który odpowiada zaawanso-waniem Flash Player 4, oraz Flash Lite 2.0, będą-cy odpowiednikiem internetowego odtwarzacza w wersji 7. Obie edycje występują w większości modeli telefonów Nokia i Sony Ericsson, będą-cych w globalnej dystrybucji od 2006 r. Najnow-sze modele fińskiego producenta posiadają za-instalowany odtwarzacz w wersji 3.0, który ma możliwości zbliżone do Flash Player 8. Ta naj-nowsza technologia komórkowa pozwala mię-dzy innymi na odtwarzanie klipów wideo w standardach H.264, On2 VP6 oraz Sorenson.

W naszym artykule skoncentrujemy się na prostym przykładzie gry przygotowanej dla od-twarzacza Flash Lite 2.0. Przygotowanie aplika-cji we wcześniejszej wersji umożliwia dotarcie do większej ilości telefonów, a przy tym nie ma problemu z odtwarzaniem tych plików w naj-nowszych komórkach z FL 3.0. Zaprezentujemy tworzenie gry mobilnej, wykazując integrację

rozwiązań graficzno-programistycznych, która jest ogromnym ułatwieniem w procesie projek-towania efektywnych i efektownych rozwiązań.

Tworzenie gry – pierwsze krokiW tej części opiszemy krok po kroku, w jaki spo-sób stworzyć prostą grę w środowisku Flash Lite 2.0 przy pomocy programu Adobe Flash CS4. Na potrzeby tego artykułu powstała wszystkim dobrze znana rozgrywka o nazwie 3 kubki.

Zaczniemy od stworzenia nowego pliku. Z menu File wybieramy opcję New, a następnie Flash File (Mobile), co obrazuje Rysunek 1. Zo-staniemy przeniesieni do centrum obsługi urzą-dzeń mobilnych Adobe Device Central CS4. Tam musimy wybrać odpowiednie parametry, w naszym przypadku będzie to Flash Lite 2.0 oraz ActionScript 2.0. Kolejną niezbędną rze-czą jest wybór urządzenia, na którym będziemy testować aplikację. Z biblioteki umieszczonej po lewej stronie wybieramy urządzenie, na potrze-by obecnego przykładu będzie to Nokia E75. W trakcie tworzenia naszej aplikacji możemy zmie-niać dowolnie obszar roboczy oraz testować go-towe pliki na różnych urządzeniach. Rysunek 2 przedstawia Adobe Device Central, który po-zwala nam na testowanie naszej gry, symulując warunki, z jakimi spotkać się może gracz, na co dzień obcując z naszym dziełem. Możemy m.in. ściemniać i rozjaśniać ekran, nałożyć na ekran refleksy świetlne słonecznego dnia, obracać ko-mórkę o 90 stopni, włączyć automatyczne wyga-szenie ekranu po dowolnej ilości sekund.

Kolejnym krokiem będzie ustawienie właści-wości dla aplikacji. Prędkość wyświetlania anima-cji nie powinna przekraczać 32 klatek na sekun-dę, gdyż komórki mają problemy z wydajnością przy wyższych parametrach odtwarzania. Roz-dzielczość jest ustalona automatycznie przy wy-

borze urządzenia testującego, w naszym przypad-ku jest to 240x320 pikseli, która jest obecnie naj-bardziej powszechną dla wyświetlaczy komórko-wych. Na tak przygotowanym dokumencie mo-żemy zacząć tworzyć aplikację. Przejdźmy do sa-mego procesu tworzenia gry. Pierwszym kro-kiem będzie stworzenie warstw, w których bę-dziemy przechowywać poszczególne elementy oraz obsługujący je kod. Dla gry 3 kubki stwórz-my 8 warstw przedstawionych na Rysunku 3. Na ścieżce ruchu (timeline) dodajmy w każdej war-stwie 4 klatki, które będą odpowiedzialne za po-szczególne ekrany w przygotowywanej aplikacji:

• inicjalizacja gry;• animacja wstępna;• rozgrywka;• koniec.

W procesie inicjalizacji gry programista mu-si zadbać o to, aby aplikacja była wyświetlana w trybie pełnoekranowym oraz aby gracz in-tuicyjnie obsługiwał aplikację za pomocą głów-nych klawiszy sterujących w komórce. Oprócz podstawowej nawigacji obsługiwanej przez kla-wisze kierunkowe (joystick/D-Pad) istotną ro-lę odgrywają również klawisze funkcyjne, tzw. SoftKeys. Znajdują się przeważnie pod ekra-nem komórki, po lewej i prawej stronie joystic-k'a. W przypadku modeli Nokia przyjmuje się standard, w którym lewy SoftKey służy do po-twierdzania bądź wywoływania menu pod-ręcznego, a prawy służy do przechodzenia o po-ziom w górę w strukturze aplikacji [Back] bądź do opcji wyjścia [Quit]. Mając na względzie wy-godę użytkowania, podpisujmy na każdym z poziomów aplikacji, do czego służą owe przyci-ski funkcyjne. Umożliwi to swobodne porusza-nie się w hierarchii naszego produktu. W na-szym przykładzie, widocznym na Rysunku 4, podpisy do klawiszy SoftKey zostały umiesz-czone dla czytelności na jasnej, drewnianej bel-ce przy dolnej krawędzi ekranu.

Integracja kodu i grafikiInicjalizacja aplikacji pokazana jest na Listin-gu 1. Kod jest umieszczony w pierwszej klat-

Flash Lite

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ć.

Dowiesz się:• Jak stworzyć prostą grę w środowisku Flash

Lite 2.0;• Jak właściwie zaprojektować rozwiązania

graficzne.

Powinieneś wiedzieć:• Podstawy technologii Flash;• Podstawy ActionScript 2.0.

Poziom trudności

Tworzenie mobilnej aplikacji w technologii Flash

Page 207: SDJ Extra 34 Biblia

206

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 207

ce utworzonej przez nas warstwy ActionScript. Warto zwrócić również uwagę na typ skryptów, które sterują procesami komórki, takimi jak ja-kość wyświetlania grafiki (SetQuality), wibra-cja (StartVibrate), ustalenie klawiszy funkcyj-nych (SetSoftKeys). Wszelkie działania tego typu odbywają się przez komendy FSCommand i FSCommand2.Warto dokładnie zapoznać się z tym zestawem ActionScript podczas opracowywania własnych rozwiązań w tej technologii mobilnej.

Kiedy już mamy zainicjowaną aplikację, mo-żemy przejść do drugiego ekranu, gdzie będzie się znajdować animacja wprowadzająca zawod-nika do gry. W naszym przypadku jest to ani-mowanie chowania monety pod kubek. Po wy-konaniu animacji gra przechodzi do klatki nr 3, w której zadeklarowana jest cała rozgrywka. Teraz na warstwie Button dodajemy na plan-szy przycisk odpowiedzialny za działanie klawi-szy joystick’a. Do tego celu rysujemy na planszy obiekt – niech to będzie prostokąt, za pomocą narzędzia Rectangle Tool [R]. Utworzony obiekt konwertujemy na Button, wybierając z menu Modify/Convert to symbol, co widać na Rysun-ku 5. Do tak utworzonego przycisku dodajemy skrypt przedstawiony na Listingu 2.

Zasadnicza część rozgrywki odbywa się w klatce trzeciej, ale zanim przejdziemy do pisa-nia kodu, omówmy, jakie elementy będą nam potrzebne do stworzenia gry. Zacznijmy od war-stwy Background, w której umieszczamy tło apli-kacji. Po dodaniu tła blokujemy warstwę, aby nie dokonać przypadkowych zmian w niej, co przedstawiamy na Rysunku 6.

W warstwie Background ustawiamy grafikę tła dla gry w postaci faktury desek narysowanej odręcznie za pomocą narzędzia Brush Tool [B]. Jest to warstwa ustawiona najniżej, co skutkuje tym, że wszystkie elementy z wyższych warstw będą nad naszym tłem.

Dodatkowo ustalamy kolor tła naszej aplika-cji. Aby tego dokonać, musimy kliknąć prawym przyciskiem myszy na pusty, szary obszar poza sceną. Pojawi nam się podręczne menu, z które-go wybieramy Document properties. Z tych opcji wybieramy Background color (ikonka kwadratu, wyświetlająca aktualny kolor podłoża – domyśl-nie jest nim biały). Rozwinie nam się paleta kolo-rystyczna, nad którą znajduje się pole tekstowe z wartością aktualnego koloru (dla białego wartość ta to #FFFFFF). Zmieniamy standardową biel na kolor jasny brązowy o wartości #D4A76E.

Na warstwie Ball rysujemy przedmiot, który będzie przesuwany pod kubkami. Nasza gra bę-dzie utrzymana w konwencji pirackiej tawerny, więc pod kubkami znajdować się będzie złota moneta. Konwertujemy utworzoną grafikę do MovieClip'a, nadajemy nazwę MyBall. Następ-na warstwa Arrow odpowiedzialna będzie za wyświetlenie animowanego wskaźnika, którym użytkownik będzie wskazywał kubek z monetą – w tym przypadku będzie to zaciśnięta dłoń z wyprostowanym palcem wskazującym. Naryso-waną dłoń konwertujemy do MovieClip'a i na- Rysunek 1. Tworzenie nowego pliku

Listing 1. Inicjalizacja

// ustawienie aplikacji w trybie pełnego ekranu

fscommand2("FullScreen", true);

// ustawienie wysokiej jakości grafiki dla odtwarzanej aplikacji

fscommand2("SetQuality", "high");

// obsługa przycisków funkcyjnych komórki

fscommand2("SetSoftkeys", "LeftSoftKey", "RightSoftKey ");

Key.removeListener(myListener);

var myListener:Object = new Object();

myListener.onKeyDown = function() {

var keyCode = Key.getCode();

if (keyCode == ExtendedKey.SOFT1) {

/* w zależności od tego, w której klatce będziemy się znajdować zostanie wywołane

inne zdarzenie. Obsługa klawisza SOFT1 będzie wywołana w

klatkach 3 i 4. */

switch(_root._currentframe){

/* Lewy SoftKey w klatce 3 odpowiada za funkcję EnterKeyPress sprawdzającą czy pod

kubkiem znajduje się moneta */

case 3 : EnterKeyPress(); break;

// Lewy SoftKey w klatce 4 odpowiada za restart aplikacji

case 4 : _root.gotoAndPlay(1); break;

}

}

if (keyCode == ExtendedKey.SOFT2) {

switch(_root._currentframe){

/* w pierwszej klatce prawy przycisk SOFT2 odpowiada za przejście na następną

klatkę gdzie zaczyna się animacja */

case 1 : play(); break;

/* w klatkach 3 i 4 SOFT2 odpowiada za wyjście z gry */

case 3 : fscommand2("quit"); break;

case 4 : fscommand2("quit"); break;

}

}

};

Key.addListener(myListener);

/* ustawienie ilości ‘żyć’ dla gracza oraz numeru planszy */

var level:Number = 1;

var lives:Number = 3;

stop();

Page 208: SDJ Extra 34 Biblia

208

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 209

zywamy MyArrow , co obrazuje Rysunek 7. We-wnątrz MovieClip'a MyArrow konwertujemy obiekt wskazującej dłoni na MC o nazwie My-ArrowIside, następnie stawiamy klatki kluczo-

we na listwie czasowej w klatkach pierwszej, ósmej i szesnastej. Zaznaczamy wszystkie klat-ki i klikamy na nich prawym przyciskiem my-szy, wybierając opcję automatycznego ruchu Create Motion Tween. Odznaczamy zaznaczenie i wskazujemy kursorem klatkę kluczową nu-mer osiem, zaznaczamy MyArrowIside i prze-suwamy go w linii prostej o kilka pikseli w gó-rę. Wychodzimy z MovieClip'a na scenę głów-ną. Tym sposobem zrobiliśmy animację wska-zującej dłoni. Każdemu z utworzonych obiek-tów nadajemy unikalną nazwę Instance Name w zakładce Properties, dzięki czemu będziemy mo-gli się do niego odwołać w prosty sposób z ko-du AS. I tak stworzone obiekty nazywamy ko-lejno arrow_mc i ball_mc. Rysunek 7 pokazu-je okienko Properties z wpisaną nazwą instancji – arrow_mc.

W warstwie ResultAnimation tworzy-my animacje, które będą wyświetlane przy prawidłowym/błędnym wyborze kubka przez użytkownika. Animacje konwertujemy do MovieClip’ów i nadajemy Instance Name odpowiednio good_mc oraz bad_mc. Utwo-rzone MC ustawiamy poza sceną, aby były nie widoczne dla gracza w czasie rozgryw-ki, będziemy je animować w momencie wy-boru kubka.

Kolejna warstwa – Top odpowiedzialna bę-dzie za wyświetlanie poziomu, na którym się aktualnie znajduje gracz oraz ilości żyć, które mu pozostały. W tym celu tworzymy na sce-nie dwa teksty statyczne oraz dwa teksty dyna-miczne, co obrazują Rysunki 8 i 9. Dla wersji dy-namicznych w zakładce Options ustawiamy Va-riable dla pierwszego pola na level, a dla drugie-go lives. Dzięki tej operacji będziemy mogli od-woływać się do utworzonych dynamicznych tek-stów bezpośrednio z poziomu kodu za pomocą zmiennych level i lives.

Należy pamiętać o zmianie rodzaju tekstu w przyborniku na DynamicText, która jest przed-stawiona na Rysunku 10. Z właściwości tekstu w panelu Properties wybieramy ładną i przede

wszystkim czytelną na małych wyświetlaczach czcionkę – w tym przypadku jest to Trebuchet MS. Zmieniamy wartość koloru tekstów z do-myślnej na #432F01, która bardziej pasuje do stylu pozostałych grafik. Flash CS4 daje nam też możliwość wyboru pomiędzy kilkoma opcjami wyświetlania czcionek. W rozwijalnej liście w przyborniku, tuż pod ikonkami justowania tek-stu, mamy dostępne następujące opcje:

• Use Device Fonts – Flash Lite Player wy-świetli czcionkę, która jest standardowym krojem danego modelu telefonu;

• Bitmap text – tekst jest pozbawiony anty-aliasing'u, nie polecamy tej opcji;

• Anti-Alias for Animation – włącza anty-aliasing czcionki, metoda rekomendowana przy tekstach animowanych;

• Anti-Alias for Readibility – metoda uwzględniająca anty-aliasing poprawiający czytelność fontu, w tym przypadku sko-rzystamy właśnie z niej;

• Custom Anti-Alias – w tym przypadku sa-mi ustalamy opcję anty-aliasing'u.

Jeśli nie korzystamy z opcji Use Device Fonts, pamiętajmy również o załączeniu czcionki do każdego dynamicznego pola tekstowego. Jeśli tego nie zrobimy, to aplikacja nie będzie w sta-nie wyświetlić danego kroju pisma i zastąpi je domyślną czcionką urządzenia. Aby zamie-ścić font, kliknijmy przycisk Embed, znajdują-cy się tuż obok rozwijalnej listy opcji wyświe-tlania fontów. Pojawi się okienko Character Em-bedding, w którym możemy zaznaczyć, które zestawy znaków zamierzamy dołączyć do dy-namicznego pola tekstowego. Wybierzmy tyl-ko te, które będą używane. W tym przypadku jest to zestaw cyfr Numerals 0-9. Każdy zestaw to dodatkowy rozmiar zwiększający plik SWF. Starajmy się również trzymać jednego kroju.

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

Rysunek 3. Warstwy aplikacji

Listing 2. Kod niewidzialnego przycisku do sterowania wskaźnikiem

on (keyPress "<Enter>") {

switch(_root._currentframe){

case 1 : play(); break;

/* w klatce 3 przycisk będzie

odpowiedzialny za wywołanie funkcji

sprawdzającej zawartość kubka */

case 3 : _root.EnterKeyPress();

break;

/* restart gry – zerowanie ‘żyć’

i planszy oraz powrót do wstępnego

menu gry */

case 4 : {

_root.gotoAndPlay(1);

level = 1;

lives = 3;

break;

}

}

}

// obsługa wskaźnika przy ruchu w lewo

on (keyPress "<Left>") {

if(_root._currentframe == 3)

if (arrow_mc.Position != 1) {

arrow_mc.Position--;

arrow_mc._x -= 70;

}

}

// obsługa wskaźnika przy ruchu w prawo

on (keyPress "<Right>") {

if(_root._currentframe == 3)

if (arrow_mc.Position != 3) {

arrow_mc.Position++;

arrow_mc._x += 70;

}

}

Page 209: SDJ Extra 34 Biblia

208

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 209

Czcionki są elementami, które znacząco wpły-wają na rozmiar finalnego pliku.

Programowanie i testowanieDo tak przygotowanej sceny możemy zacząć pro-gramowanie rozgrywki, przedstawione szczegó-łowo na Listingu 3. Dodatkowo dla MovieCli-p’ów wyświetlających nam, czy prawidłowo wy-braliśmy kubek, ustawiamy skrypt przenoszący nas ponownie do losowania kubków, bądź do ekranu końcowego aplikacji w przypadku wyko-rzystania wszystkich żyć. Listing 4 przedstawia proste rozwiązanie zamykające rozgrywkę. W ostatniej klatce animacji wyświetlamy podsumo-wanie gry, w którym gracz otrzymuje informację, do jakiego poziomu dotarł. Do tego posłuży nam pole Text Tool. Ustawiamy jego parametry na Dy-namic Text,oraz nadajemy wartość Variable =

level. W ten sposób pole automatycznie pobie-rze wartość zmiennej level do tego pola.

Tak stworzoną aplikację uruchamiamy w Adobe Device Central, wybierając z menu głów-nego Flash CS4 zakładkę Control/Test Movie [Ctr-

l+Enter]. W Adobe Device Central developer ma możliwość testowania programu na różnych te-lefonach, co dobrze widać w lewej części Rysun-ku 11. Po wyborze interesującego nas telefonu w ekranie EMULATOR mamy podgląd apli-kacji. W tym miejscu możemy testować funk-cjonalność naszej gry, sprawdzić czy klawisze są prawidłowo podpięte oraz czy aplikacja działa w trybie pełnoekranowym. Dodatkowo po pra-wej stronie znajduje się zakładka MEMORY, w której możemy obserwować zachowanie się pa-mięci telefonu w trakcie działania poszczegól-nych etapów gry. Adobe Device Central dosto-sowuje pamięć emulowanego telefonu w zależ-ności od wybranego modelu. Dodatkową opcją jest sprawdzenie działania aplikacji w trzech try-bach jakości: High, Medium i Low. Do tego celu wyświetlamy zakładkę Device Performance, gdzie mamy możliwość swobodnego manipulowania tymi opcjami.

Techniczne aspekty mamy już za sobą, skup-my się więc na istotnych elementach dotyczą-cych projektowania w środowisku Flash Li-

te, gdyż trudno sobie wyobrazić gry i animacje Flash bez dobrze przygotowanej grafiki.

Im mniej tym lepiejTo złota zasada w tej technologii. Nie należy przesadzać z nadmiarem i skomplikowaniem wizualnych obiektów oraz animacji. Obciąży to

Rysunek 5. Konwersja obiektu

Rysunek 6. Blokada nieużywanych warstw

Rysunek 7. Nadawanie nazw obiektom

Rysunek 8. Punktacja – teksty statyczne

Rysunek 9. Punktacja – teksty dynamiczne

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

Page 210: SDJ Extra 34 Biblia

210

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 211

bowiem stosunkowo słaby (ok. 200 – 400 Mhz) procesor komórki, którego nie wspomagają, jak w przypadku PC, karta graficzna czy pamięć RAM. Dlatego elementy gry powinny być pro-ste i wyraźne. Widoczność jest tu również prio-rytetem. Biorąc pod uwagę standardy wyświe-tlaczy mobilnych (176x208 pikseli w przypad-ku starszych modeli i 240x320 w przypadku nowszych), wyeliminujmy wszelkie szczegó-ły obiektów, które pozostaną niezauważone dla użytkownika. Czy rzeczywiście główny bohater gry musi mieć guziki na koszuli, która zajmuje 5 na 5 pikseli ekranu? Rezygnując z tego typu de-tali, zaoszczędzimy nie tylko na rozmiarze pliku, ale również pozostawimy sporo cennego miejsca

w podręcznej pamięci telefonu (tzw. heap me-mory) dla innych, niezbędnych procesów gry.

Flash bazuje również na symbolach, którymi są Graphics (symbole graficzne) jak i MovieCli-p’y. Raz stworzony obiekt przechowywany jest w bibliotece pliku FLA. Dzięki temu możemy użyć dowolnej ilości duplikatów tego samego symbolu na scenie, a program będzie musiał je-dynie przeliczyć oryginał, znajdujący się w pa-nelu biblioteki [Library]. Starajmy się nie dokła-dać kolejnych obiektów bez potrzeby i wykorzy-stywać do wielu zastosowań te, które już stwo-rzyliśmy. To także oszczędność pamięci i roz-miaru. W przykładzie 3 kubki takie rozwiąza-nie również ma miejsce – widać to na Rysunku

4. Stworzona grafika jasnej, drewnianej deski zo-stała wykorzystana u góry ekranu, pod tekstami wyświetlającymi punkty i życie, następnie jej ko-pia za pomocą opcji Menu/Transform/FlipVertical odbita lustrzanie względem osi X i umieszczona przy dolnej krawędzi ekranu jako pole dla opisa-nia klawiszy SoftKeys.

Dobrym nawykiem jest również tworzenie grafiki w oparciu o proste kształty, złożone z jak najmniejszej ilości punktów. Każdy punkt to ko-lejne bajty oraz dodatkowy wysiłek dla mobilne-go procesora.

Aby zoptymalizować naszpikowany punkta-mi kształt możemy posłużyć się opcją ich auto-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 11. Testowanie aplikacji w Adobe Device Central

Rysunek 12. Automatyczna optymalizacja obiektów graficznych

Rysunek 13. Okienko optymalizacji

Rysunek 14. Dostępne efekty graficzne dla obiektów

Rysunek 15. Optymalizacja animacji dla Flash Lite

Page 211: SDJ Extra 34 Biblia

210

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 211

Listing 3. Engine gry

center = 0;

// zmienna whichRoll odpowiedzialna za zliczanie ilości

mieszań kubka

whichRoll = 0;

// oneEnter sprawdza czy kubki są w danym momencie animowane

(1=true, 0=false)

oneEnter = 1;

// pozycja, na której aktualnie znajduje się moneta

ball_mc.Position = 1;

// pozycja kursora do wskazywania kubka

arrow_mc.Position = 1;

arrow_mc._visible = false;

arrow_mc._x = 33;

// zmienna blokująca możliwość sprawdzania zawartości kubków

w czasie mieszania

var canKeyPress:Boolean = false

// zmienna nextBlend odpowiada za wybór, które kubki będą

animowane

nextBlend = (random(300)%3)+1;

// dynamiczne tworzenie 3 kubków na scenie

var kub1:MovieClip = attachMovie("MyKubek","cupx1",103,{_x:

20,_y:130});

var kub2:MovieClip = attachMovie("MyKubek","cupx2",102,{_x:

90,_y:130});

var kub3:MovieClip = attachMovie("MyKubek","cupx3",101,{_x:

160,_y:130});

kub1.NR = 1;

kub2.NR = 2;

kub3.NR = 3;

/* włączamy ciągłe odtwarzanie klatki, czego skutkiem będzie

powtarzanie losowania na zadanych przez nas warunkach do

momentu utraty ‘żyć’ przez zawodnika */

onEnterFrame = function (){

switch(level){

/* ustawienie prędkości po dojściu do poszczególnych

poziomów w rozgrywce */

case 1 : speed = 3; break;

case 3 : speed = 5; break;

case 6 : speed = 7; break;

}

switch(nextBlend){

/* w zależności od wylosowanej liczby odtwarzanie jednego z

trzech możliwych mieszań kubków */

case 1 : roll3(kub1,kub2,kub3); break;

case 2 : roll3(kub1,kub3,kub2); break;

case 3 : roll3(kub2,kub3,kub1); break;

case 0 : {

arrow_mc._visible = true;

delete this.onEnterFrame;

break;

}

}

/* ilość zamian kubków zależy od poziomu, na którym się

znajdujemy, a osiągnięcie danego poziomu zatrzymuje

OnEnterFrame */

if(whichRoll == level){

nextBlend=0;

canKeyPress = true;

}

};

/* funkcja odpowiedzialna za mieszanie kubków */

function roll3(cup1:MovieClip,cup2:MovieClip) {

if (center == 0) {

/* przemieszczaj kubki na scenie */

if (cup1._x<cup2._x) {

cup1._y+=speed;

cup1._x+=speed;

cup2._y-=speed;

cup2._x-=speed;

} else {

center = 1;

}

}

if (center == 1) {

if (cup1._y>130) {

cup1._y-=speed;

cup1._x+=speed;

cup2._y+=speed;

cup2._x-=speed;

oneEnter=0;

} else

/* warunek sprawdzający czy zamiana kubków dobiegła końca */

if(center ==1 && cup1._y == 130 && oneEnter==0)

{

center = 0;

/* wylosowanie następnego zestawu kubków do zamiany */

nextBlend = (random(300)%3)+1;

/* zwiększenie licznika losowań */

whichRoll++;

oneEnter=1;

/* dla uproszczenia wykonywania animacji ustaw kubki w

pozycji w jakiej znajdowały się podczas startu tej klatki */

if(cup1.NR == 1 && cup2.NR== 2){

cup1._x = 20;

cup2._x = 90;

if(ball_mc.Position == 1) ball_mc.Position = 2;

else

if(ball_mc.Position == 2) ball_mc.Position = 1;

return;

}

else

if(cup1.NR == 1 && cup2.NR == 3){

cup1._x = 20;

cup2._x = 160;

if(ball_mc.Position == 1) ball_mc.Position = 3;

else

if(ball_mc.Position == 3) ball_mc.Position = 1;

return;

}

else

if(cup1.NR == 2 && cup2.NR == 3){

cup1._x = 90;

cup2._x = 160;

if(ball_mc.Position == 2) ball_mc.Position =3;

else

if(ball_mc.Position == 3) ball_mc.Position =2;

return;

}

}

}

Page 212: SDJ Extra 34 Biblia

212

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 213

nie usunięta. Istnieje także ręczna metoda po-zbycia się punktów za pomocą narzędzia Subse-lectional Tool. Jest to ikonka z białą strzałką znaj-dującą się w palecie narzędzi lub przypisana do skrótu klawiszowego [A]. Za jej pomocą wy-bieramy kształt, wraz z nim podświetlą się jego wszystkie punkty składowe. Następnie nie od-znaczając obiektu, klikamy w te, których chce-my się pozbyć (z przyciskiem [Shift] mamy moż-liwość dodawania kolejnych punktów do zazna-czenia), i usuwamy je za pomocą przycisku [De-lete]. Użycie pierwszego sposobu optymalizacji obiektu jest prostsze i szybsze, za to mniej do-kładne, bo automatyczne. Drugi sposób wyma-

ga wprawy i cierpliwości, jednak daje pełną kon-trolę nad ilością punktów i finalnym kształtem.

W przypadku gdy musimy użyć skompliko-wanego graficznie elementu, zastosowanie ma-py bitowej jest wyjściem z sytuacji. Bitmapa co prawda zwiększy rozmiar swf'a, jest jednak ła-twiejsza do przetworzenia przez Flash Lite Play-er'a, niż wektorowy kształt, który jest tworzony od podstaw w odtwarzaczu i na bieżąco przeli-czany. Program Flash CS4 Professional oferuje nam również kilka efektów, które możemy za-stosować dla tworzonych symboli graficznych. Są one dostępne w rozwijanej liście Color Effect w panelu Properties, przedstawione na Rysunku

14. Ze szczególną ostrożnością powinno się do-dawać efekt przezroczystości (Alpha). Zbyt czę-ste jego stosowanie może doprowadzić do obcią-żenia pamięci telefonu i spowolnić proces od-twarzania pliku SWF.

Animacje i optymalizacjeFlash to nie tylko potężne narzędzie graficzne, ale również środowisko animacyjne. Automa-tyzacja ruchu obiektów i kształtów (motion twe-en i shape tween) czyni proces animacji niezwy-kle łatwym. W przypadku wersji Lite ze wzglę-dów optymalizacyjnych nie powinno się jednak 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 animacji

Rysunek 18. Automatyczne zaznaczanie zbędnych obiektów z biblioteki

Rysunek 16. Zamiana klatek automatycznego ruchu na ruch poklatkowy

Page 213: SDJ Extra 34 Biblia

212

Rozwiązania mobilne

SDJ Extra 34 Biblia

Flash Lite

www.sdjournal.org 213

ją i ponownie posługujemy się prawym przyci-skiem myszy, z menu wybierając tym razem Re-move Tween – Rysunek 17 obrazuje ten krok. Au-tomatyczny ruch możemy wyłączyć również w panelu Properities, wybierając z rozwijalnej listy Tween pozycję None.

Eksportując finalną wersję gry, usuńmy wszystkie niepotrzebne linijki kodu, komenta-rze, warstwy, puste klatki. Pozostawmy tylko to, co niezbędne jest do prawidłowego funkcjono-wania naszej gry. Jeśli w bibliotece trzymaliśmy warstwy i obiekty w katalogach, pozbądźmy się tej struktury, gdyż foldery niepotrzebnie zwięk-szają rozmiar finalnego pliku.

Skorzystajmy z funkcji biblioteki zaznaczają-cej nieużywane obiekty – z menu Window wy-bierzmy zakładkę Library, bądź skorzystajmy ze skrótu [Ctrl+L]. Pojawi się okno biblioteki. Tak jak na Rysunku 18, kliknijmy na ikonkę w pra-wym górnym rogu okienka Library i z rozwijal-nych opcji zaznaczmy Select Unused Items. Na-stępnie usuńmy niepotrzebne obiekty za po-mocą ikonki kosza, znajdującej się przy dolnej

krawędzi okienka Biblioteki. Tym sposobem zaoszczędziliśmy kilobajtów na i tak niewyko-rzystywanych obiektach, które zaśmiecały plik, i uporządkowaliśmy jego strukturę. Nie należy również stosować Scen (Window/Other panels/Scene), które są prawdziwymi pożeraczami baj-tów. Lepiej poukładać zawartość aplikacji w mo-vie clip'ach i skryptowo je odgrywać.

PodsumowanieFlash Lite to środowisko niezwykle ciekawe, dające wiele możliwości, a jednocześnie przy-jazne dla twórców wszelkiego rodzaju con-tent’u. Biorąc pod uwagę nasze wskazówki,

pamiętajmy, aby nie popadać w skrajności. Zdroworozsądkowe podejście w zabawie z Flash Lite'em polega na znalezieniu kompro-misu pomiędzy szybkim i prawidłowym dzia-łaniem gry oraz ciekawą i czytelną szatą gra-ficzną. W osiągnięciu tego celu nie rezygnuj-my z wszystkich dobrodziejstw tej platformy – eksperymentujmy oraz przede wszystkim testujmy nasz produkt na możliwie najwięk-szej ilości telefonów komórkowych. Modele różnią się między sobą parametrami, dlatego taka konfrontacja z docelowymi urządzenia-mi pozwoli nam się zorientować w rzeczywi-stych możliwościach platformy.

GRZEGORZ TRUBIŁOWICZCEO firmy IKS, pasjonat marketingu i nowych technologii.

MATEUSZ PIETREKCreative Director IKS’a, doświadczony projektant i animator.

KAROL BEDNARZJunior Flash Developer w firmie IKS, specjalista Flash Lite.

IKS Mobile to dział firmy IKS odpowiedzialny za międzynarodowe sukcesy gier i rozwiązań mobilnych. Eksperci od technologii Flash Lite, którą zajmują się nieprzerwanie od 2005 roku. Gry „Crazy Matches”, „Tropictos” oraz „Cubic Republic” były docenione przez profesjonalne jury i międzynarodową publicz-ność m.in. w konkursach IMGA, IGF, Flash Lite Developer Challenge. Więcej informacji na www.IKSmo-bile.com oraz www.iks.pl. Kontakt z autorami: [email protected]

Listing 3cd. Engine gry

Listing 4. Zakończenie rozgrywki

}

/* funkcja wywoływana po wciśnięciu przycisku LeftSoftKey

i Enter w czasie rozgrywki, odpowiedzialna jest za

sprawdzenie zawartości kubka */

function EnterKeyPress(){

if(canKeyPress==true){

canKeyPress = false;

ball_mc._x = 34 + ((ball_mc.Position - 1) * 70);

if(ball_mc.Position == 1)

_root.kub1.gotoAndPlay("anim");

if(ball_mc.Position == 2)

_root.kub2.gotoAndPlay("anim");

if(ball_mc.Position == 3)

_root.kub3.gotoAndPlay("anim");

/* w przypadku prawidłowego wskazania kubka zwiększ poziom o

1 i wyświetl animację good_mc */

if (arrow_mc.Position == ball_mc.Position) {

level++;

good_mc._x = 60;

good_mc.gotoAndPlay(1);

/* złe wytypowanie kubka powoduje utratę życia i wyświetlenie

animacji z klipu bad_mc*/

} else {

lives--;

bad_mc._x = 60;

bad_mc.gotoAndPlay(1);

}

}

}

stop();

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

ekranie końcowym ani przy ponownym

wyświetleniu animacji startującej

grę */

_root.kub1.removeMovieClip();

_root.kub2.removeMovieClip();

_root.kub3.removeMovieClip();

/* w momencie błędnego wytypowania kubka przechodzimy

do klatki 5, którą nazywamy dla

uproszczenia ‘gameOver’ */

if (_root.lives == 0) {

_root.gotoAndStop("gameOver");

/* poprawne wytypowanie kubka wraca gracza do animacji w

klatce drugiej, po której następuje

kolejna runda gry */

} else {

_root.gotoAndPlay(2);

}

W Sieci

• ht tp : //w w w.adobe.com /produc ts/flashlite/;

• http://www.adobe.com/devnet/devices/;• http://www.flashlite.info.pl.

Page 214: SDJ Extra 34 Biblia

Felieton

214 SDJ Extra 34 Biblia

Felieton

www.sdjournal.org 215

Jednym z najważniejszych towarów naszych czasów jest informa-cja. Transformacje społeczeństw odbywają się w kierunku coraz sprawniejszego posługiwania się wszelkimi danymi. Kluczem do

sukcesu jest umiejętność artykułowania pytań na sposób cyfrowy oraz odszukiwania i kategoryzacji odpowiedzi na nie. To motywuje do roz-wijania wszelkich technologii zwiększających możliwości przesyłania i przetwarzania informacji. Obserwujemy postępujące zapotrzebowa-nie na usprawnienie dystrybucji informacji, co ma umożliwić dociera-nie do maksymalnej liczby odbiorców. Rośnie zapotrzebowanie na no-we formy prezentacji, które będą zrozumiałe dla możliwie najszersze-go grona użytkowników. Celem jest dostarczanie coraz większej ilości usług, które będą osiągalne dla rosnącej populacji. Takie drogi rozwo-ju umożliwiają ograniczenie zjawiska e – wykluczenia. I nie zawsze mo-że chodzić o naszą wygodę. To może ratować życie.

Mobilne czy stacjonarne?Dostarczanie usług drogą cyfrową kojarzyło się dotychczas głównie z drogą Internetu stacjonarnego. Jeżeli jednak przyjrzymy się szczegó-łowym danym, to okaże się, że na świecie średnio na 100 ludzi Inter-netu używa 20 osób. W tym samym czasie subskrybentów telefonii mobilnej jest aż 50. W poszczególnych regionach dysproporcje te są jeszcze większe – na przykład w Afryce prawie sześciokrotnie więcej ludzi ma dostęp do telefonu komórkowego niż do Internetu (zob. [1]). Również dynamika wzrostu użytkowników Internetu jest dużo niższa, niż ilość klientów przybywających operatorom komórkowym. Ceny połączeń głosowych i SMS'ów regularnie spadają, również w sensie koszyków usług (zestawienie oparte na procentowym udziale połą-czeń w ramach sieci abonenta, poza nią, do sieci stacjonarnych, w szczycie i poza nim, w weekendy itp.). Nic dziwnego, że to właśnie łą-cza ruchome zyskują coraz większą popularność jako medium stoso-wane do dostarczania usług.

m-InternetZgodnie z ostatnim raportem UKE (zob. [2]) w Polsce z dostępu do In-ternetu za pośrednictwem sieci ruchomych (modem bezprzewodo-wy, np. Orange Free, iPlus, Blueconnect czy Play Online) do końca 2008 roku skorzystało około miliona osób. Dodatkowo za pomocą komór-ki z siecią łączyło się około 2 mln osób. Mimo dynamicznego wzrostu, forma ta stanowi jedynie 3% obrotu europejskich operatorów ([3]). Jest to zapewne spowodowane zbyt wolnym rozwojem technologii 3G zapewniających odpowiedni transfer dla usług Web 2.0. Współ-praca rządów i konsorcjów europejskich w ramach 2G (usługi głoso-

we i SMS) sprawiła, że ich rozwój w Europie był bardzo szybki i w nie-których krajach penetracja tych usług osiągnęła nawet 150%. Nieste-ty, tak się nie stało w przypadku 3G. Operatorzy, próbujący odbić so-bie bardzo kosztowne licencje i infrastrukturę, obecnie preferują tak-tykę ograniczania klientom dostępu do szerszych treści (spoza oferty dostarczanej przez nich samych, ang. walled garden). Przykładem mo-gą być limity transferu. Dodatkowo opłaty za korzystanie z usług uza-leżniane są od stopnia ich wykorzystania. W przypadku 2G wprowa-dzenie opłat stałych (ang. flat rate) oraz znaczące obniżenie cen (wy-muszone również regulacjami na poziomie europejskim) doprowa-dziło do zwiększenia wolumenu sprzedaży. W efekcie przełożyło się to na zwiększenie zysków.

Taktyka przyjęta przez operatorów odbija się na popularności 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-cji miejskich. Dlatego Unia Europejska aktywnie działa na rzecz zmia-ny takiego stanu rzeczy. Wysiłki idą głównie w kierunku uporządko-wania częstotliwości oraz regulowania rozliczeń między operatorami. Z jednej strony chodzi tu o nacisk na rządy krajów członkowskich w celu udostępnienia nowych pasm i ograniczenia kosztów ich licencji, z drugiej – o regulowanie i uproszczanie rozliczeń między operatora-mi sieci. Krokiem w tym kierunku jest odgórne ograniczanie taryf ro-amingowych czy niechybna regulacja opłat za zakończenie połączeń między sieciami (ang. Mobile Termination Rates, MTR) (zob.[4]).

W obliczu licznych interwencji UE, wydaje się, że główną prze-szkodą w popularyzacji dostępu szerokopasmowego przez komór-kę są regulacje administracyjne oraz modele biznesowe przyjęte przez operatorów. Wszelkie granice technologiczne są stosunkowo szybko przesuwane. Dla przykładu: na wystawie CTIA Ericsson zade-monstrował rozwiązanie oparte na HSPA, pozwalające na przesyła-nie danych z szybkością 56 Mb/s.

m-TelewizjaOgraniczona pojemność łączy ruchomych sprawia, że strumieniowa transmisja telewizji w sieciach komórkowych (ang. unicast) jest sto-sunkowo mało atrakcyjna. Możliwości tej technologii są dosyć ogra-niczone, co wpływa ujemnie na ilość użytkowników mogących jed-nocześnie korzystać z takich usług. Alternatywą jest wykorzystanie trybu rozsiewczego (ang. multicast lub ang. broadcast) pozwalające-go na dostarczanie programu telewizyjnego do dużej ilości termina-li. W ten sposób komunikacja serwer – terminal zastępowana jest try-bem serwer – wiele terminali, co znacznie efektywniej wykorzystu-

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.

Page 215: SDJ Extra 34 Biblia

Felieton

214 SDJ Extra 34 Biblia

Felieton

www.sdjournal.org 215

je pasmo. Spektrum możliwych rozwiązań jest tu szerokie – od pro-tokołu FLO (głównie USA), wdrożonego w Korei Południowej T-DMB, japońskiego 1seg czy wreszcie zalecanego przez Komisję Europejską (KE) standardu DVB-H. Te rozwiązania wymagają najczęściej nieste-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-ciem technologii MBMS.

W Polsce testy DVB-H rozpoczęły się w 2006 roku. Konkurs na czę-stotliwości 470-790 Mhz został rozstrzygnięty w marcu tego roku. Więcej punktów uzyskała spółka INFO-TV-FM. Jej ofertę oceniono wyżej niż złożoną przez Mobile TV – spółkę utworzoną przez Polską Telefonię Cyfrową, P4, Polkomtel oraz PTK Centertel.

W trakcie formułowania konkursu nie obyło się bez sporów po-między Urzędem Komunikacji Elektronicznej (UKE) i Krajową Radą Radiofonii i Telewizji (KRRiT). UKE zakwalifikowała telewizję mobil-ną jako usługę telekomunikacyjną, a nie telewizyjną (radiodyfuzyj-ną). Taka usługa świadczona przez operatora multipleksu nie pod-legałaby pod ustawę o radiofonii i telewizji (por. [5]). Oznaczało to wyłączenie KRRiT z obowiązku wydawania zgody na rozpoczęcie nadawania i kontroli przekazywanych treści. Dodatkowe uwarun-kowania konkursu ograniczały również potencjalne zyski nadaw-ców poprzez np. faworyzowanie operatorów deklarujących więk-szą liczbę partnerów, którzy będą współtworzyć propozycję pro-gramową. Ostatecznie osiągnięte porozumienie pozostawia opera-torowi kontrolę nad zawartością multipleksu bez konieczności sta-rania się o koncesję na treści, które się na nim znajdą. Taką konce-sję będą musieli zdobyć nadawcy radiowi i telewizyjni dostarczają-cy treści ([6]). Może to zapowiadać bardzo szybki start tej usługi (co zresztą jest wpisane w warunki konkursu). Znacząco przedłużono również czas, na jaki operator uzyskuje prawo do zarządzania mul-tipleksem – do 2025 roku. Wynika to z kalkulacji, że zwrot inwesty-cji jest spodziewany po 7 latach, a następne 15 lat daje możliwość osiągnięcia zysków.

Powyższe rozważania dają już pewne wyobrażenie stopnia skomplikowania modeli biznesowych towarzyszących telewizji mobilnej. Graczy jest tu jednak znacznie więcej, jak chociażby:

• twórcy/właściciele treści (ang. content producer, owner), któ-rzy produkują programy; reklamodawcy;

• agregatorzy treści (ang. content providers) – kupują treść od twórców treści, łączą, dodają do nich reklamy itp.,dostarcza-jąc gotowy strumień zawierający audio i wideo;

• operatorzy datacastu (ang. datacast operator) – odpowiedzialni 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);

• operatorzy sieci rozgłoszeniowej (ang. broadcast network operator) – zajmujący się aspektami technicznymi transmisji, dostarczają infrastrukturę;

• operatorzy sieci komórkowej (ang. mobile network operator);• operatorzy usługi – odpowiedzialni za sprzedaż usług pod

banderą marki, kontakty z klientami etc.

Podstawowym problemem jest tu określenie, kto komu płaci i za co. Powoduje to, że nawet w krajach przodujących w tym temacie penetracja rynku nie przekracza 10% (Korea Południowa, w Euro-pie nie więcej niż 2%). Dodatkowo operatorzy są świadomi, że te-lewizja mobilna nie będzie substytutem dla telewizji stacjonarnej (analogowej czy cyfrowej), a jedynie jej uzupełnieniem. Ograniczo-na wielkość wyświetlacza oraz żywotność baterii terminali sprawia-ją, że zgodnie z badaniami będziemy skłonni do oglądania jej przez około 20 minut dziennie, głównie w środkach komunikacji publicz-nej lub np. w ramach przerwy obiadowej. Będzie to raczej ogląda-nie okazjonalne, gdy nie chcemy przegapić ulubionego programu.

We Włoszech np. najpopularniejsze są serwisy sportowe i wiadomo-ści. Spodziewany jest również popyt na klipy muzyczne, kreskówki – choć nie jest pewne, czy te dziedziny nie zostaną zdominowane przez tryb na żądanie (ang. on demand).

m-ReklamaJedną z cech wyróżniających technologie mobilne jest osobistość terminali. Jest to niezaprzeczalna zaleta w porównaniu do kompu-terów stacjonarnych czy laptopów. Komputer jest najczęściej wy-korzystywany przez kilka osób. Komórka ma zazwyczaj swojego jednego właściciela. Komputer to adres IP w sieci, telefon – to kon-kretna osoba, wymieniona z imienia, nazwiska, wieku i zawodu w umowie podpisanej z operatorem. Z punktu widzenia określania grupy docelowej jest to atrakcyjne. Łącząc takie informacje z da-nymi zawartymi w bilingach, można profilować ofertę, kierując się sposobem użytkowania telefonu. Łatwiej wtedy określać, jakie usługi mogą zainteresować użytkownika. Zwiększa to skuteczność przy jednoczesnym ograniczeniu nakładów na reklamę. Dla przy-kładu, w USA około 50% użytkowników odpowiada na otrzymane SMS-em treści reklamowe.

Treści reklamowe to głównie SMS'y (również Premium), MMS'y, fotokody. Coraz większą popularność zyskuje marketing przez łą-cze bluetooth, ale również banery na stronach mobilnych, rekla-ma w wyszukiwarkach czy w końcu reklama w telewizji mobilnej. To rynek rosnący w szybkim tempie. W 2007 roku szacowany na 2.7 mld dolarów, w 2012 roku ma zwiększyć się do prawie 20 mld dolarów.

Istnieje jednak prawdopodobieństwo, że największa zaleta ko-mórki – jej osobistość – obróci się przeciwko reklamodawcom. Re-klamy przesyłane na telefon, bardziej niż w przypadku kompute-rów stacjonarnych, mogą być uznane za ingerencję w prywatność. Zgodnie z kwalifikacją za spam uznaje się wszelkie treści, które są niezależne od tożsamości adresata i gdy zachodzi podejrzenie, że nadawca odniesie z ich tytułu korzyści (zob. [7]). Będzie to przed-miotem najbliższej nowelizacji prawa telekomunikacyjnego. Kary 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-tych. Przy okazji należy wyjaśnić kwestię zakresu zgody (wyraża-nej podczas np. zawierania umowy z operatorem) na przetwarza-nie danych osobowych. Czy implikuje ona zgodę na otrzymywa-nie treści reklamowych? Podobnie, czy skorzystanie z usługi np. pobierania logo na komórkę, automatycznie uprawnia usługo-dawcę do wysyłania reklam (tzw. zgoda dorozumienna, [7])?

Źródła

• [1] Dane: International Telecommunication Union (ITU), www.itu.int;• [2] Analiza cen usług dostępu do Internetu operatorów sieci ru-

chomych, UKE, Marzec 2009;• [3] Sprawozdanie okresowe na temat jednolitego europejskiego

rynku łączności elektronicznej w 2008 r. (sprawozdanie nr 14), Komisja Wspólnot Europejskich, Marzec 2009;

• [4] Mobile goes Internet: key challenges for mobile ubiquity in Europe's single market, Viviane Reding, February 2008;

• [5] Telewizja w komórce dzieli rynek, Magdalena Lemańska , Łu-kasz Dec, Rzeczpospolita, Kwiecień 2008;

• [6] Ruszył konkurs na operatora telewizji mobilnej, Interia PL/PAP, październik 2008;

• [7] Za uciążliwe SMS-y można będzie zapłacić nawet 100 tys. kary, Gazeta Prawna, 6 kwietnia 2009;

• [8] Vital Wave Consulting. mHealth for Development: The Op-portunity of Mobile Technology for Healthcare in the Develo-ping World. Washington D.C. and Berkshire, UK: UN Founda-tion-Vodafone Foundation, Partnership, 2009;

• [9] Apple traci monopol na aplikacje iPhone'a, Yukari Iwatani Kane, The Wall Street Journal, Dziennik, 14-15 marzec 2009;

Page 216: SDJ Extra 34 Biblia

Felieton

216 SDJ Extra 34 Biblia

Oczywiście nastawienie do reklam może ulec zmianie, jeżeli w za-mian za ich odbieranie dostaniemy darmowe minuty lub SMS'y (np. 36i6, usługa Chill Bill).

m-ZdrowieOmawiając najszybciej rozwijające się usługi ruchome, nie można pominąć usług medycznych dostarczanych tą drogą. Dla przykładu, obecnie większość centrów medycznych oferuje zamawianie i przy-pominanie o wizytach lekarskich za pomocą SMS'ów. Zalety usług ru-chomych sprawiają, że stanowią one wielką szansę na poprawienie warunków życia w krajach rozwijających się.

Charakterystyka tych regionów to przede wszystkim brak wykwa-lifikowanej kadry medycznej, utrudnienia w dostępie do placówek medycznych, szpitali, izolacja małych społeczności (poprzez prze-szkody naturalne i etniczne) czy wreszcie ubóstwo. Opublikowa-ny niedawno raport Organizacji Narodów Zjednoczonych na temat wspomagania usług medycznych w krajach rozwijających się (zob. [8]) specyfikuje następujące podstawowe kierunki rozwoju medycz-nych usług mobilnych:

• edukacja i szerzenie świadomości;• zdalne zbieranie informacji;• zdalne monitorowanie;• komunikacja i edukacja pracowników medycznych;• śledzenie ognisk epidemii;• wspomaganie diagnostyki i leczenia.

Edukacja zdaje się tu być jednym z kluczowych zadań. Niezależ-nie od dostępnych środków, to właśnie zapobieganie jest najod-powiedniejszym działaniem w sensie długofalowym. Przykładem niech będą SMS'owe quizy na temat wiedzy o AIDS czy wiado-mości zawierające informacje o możliwościach uzyskania pomo-cy. Szacuje się, że np. w RPA nawet 25% ludności może być zarażo-na HIV, z czego jedynie 3% jest tego świadoma. Dzięki opisywane-mu powyżej projektowi Masiluleke, ilość telefonów na linię pomo-cy wzrosła trzykrotnie (zob. [8]).

Aby jednak takie akcje były skuteczne, konieczne jest dostarcze-nie właściwych treści do odpowiednich grup. Wymaga to groma-dzenia informacji medycznych. Droga elektroniczna wydaje się tu najtańszym i najłatwiejszym rozwiązaniem. Pracownikom medycz-nym oszczędza się w ten sposób wysiłku na wypełnianie papierków. Umożliwia to szybką identyfikację ognisk i śledzenie zmian zasięgu chorób. Posiadanie takich informacji w oczywisty sposób zwiększa efektywność środków zaradczych.

Coraz szersze zastosowanie (i to nie tylko w krajach rozwijają-cych się) znajduje zdalne monitorowanie pacjentów (telemonito-ring). Pozwala to sprawdzać stan pacjenta przebywającego poza jednostką medyczną. Prostsze rozwiązania to np. rozdawanie cho-rym telefonów, na które dzwonią pracownicy opieki medycznej, py-tając o stan zdrowia czy przypominając o zażyciu odpowiednich le-karstw. W bardziej zaawansowanych przypadkach łączność rucho-ma jest wykorzystywana do przesyłania danych medycznych z urzą-dzeń diagnostycznych umieszczonych w domu pacjenta (waga, ci-śnieniomierz, glukozometr i inne). Dane zbierają i analizują kompu-tery w centrach medycznych, alarmując odpowiedniego lekarza w przypadku zagrożenia zdrowia.

Kolejną zaletą dostarczania informacji medycznej drogą mobilną jest możliwość dostępu do najbardziej aktualnych danych medycz-nych, epidemiologicznych czy praktyk postępowania wspomagają-cych pierwszą pomoc, diagnozowanie i leczenie. Pomaga to w niesie-niu skutecznej pomocy w miejscu, gdzie pacjent przebywa (ang. po-int of care, POC).

Wymienione powyżej praktyki mają na celu głównie zwiększenie efektywności dostarczania pomocy medycznej. Oczywistym jest fakt,

że mają one na celu obniżanie jej kosztów – tak po stronie pacjen-tów (np. brak konieczności pojawiania się w szpitalu), jak i służb me-dycznych. Także w przypadku tego typu usług będziemy mieli do czy-nienia ze stosunkowo dużą ilością uczestników modelu biznesowe-go. Dostarczanie takich usług wymaga współdziałania rządów, ich agend odpowiedzialnych za opiekę zdrowotną, biznesu dostarcza-jącego odpowiednie urządzenia i aplikacje obsługujące usługi oraz platformy umożliwiające ich działanie. Tylko synchronizacja ich wy-siłków połączona z (nie ukrywajmy) możliwością osiągnięcia zysków będzie sprzyjała rozwojowi usług, które już niejednokrotnie dowiodły swojej skuteczności.

Aplikacje i platformyDostęp do powyższych usług wymaga tworzenia nowych plat-form i aplikacji. Jest to kolejny trend warty wspomnienia co naj-mniej ze względu na 2 produkty: AppStore oraz Android.

Pisanie aplikacji na iPhona stało się już niemal zjawiskiem socjo-logicznym, głównie ze względu na krążące historie o krociowych zyskach. Kluczem jest tu oryginalny pomysł, którym uda się zarazić odpowiednią ilość użytkowników (głównie chodzi o rynek ame-rykański). Kilka dolarów za aplikację pomnożą przez tysiące po-brań. Popularność tego biznesu przekroczyła wszelkie oczekiwa-nia – AppStore właśnie przekroczy miliard pobrań. Wydaje się, że wszyscy są zadowoleni: programiści mogą szybko zarobić, Apple pobiera z tego nawet 30% (zob. [9]), użytkownicy cieszą się nowy-mi możliwościami swojej zabawki uzyskanymi za cenę hamburge-ra. Pomysł Appla z certyfikacją aplikacji pozwolił na zdobycie po-kaźnego źródła dochodu – szacowanego na nawet 800 milionów dolarów w tym roku (zob. [9]). Jest to całkowicie inny model niż ten przyjęty np. przez Microsoft na systemach Windows Mobile. Nie ma tam ograniczeń co do publikowania aplikacji, nawet jeżeli są one konkurencyjne do tych oferowanych przez sam koncern.

Z drugiej jednak strony, wymaganie certyfikacji oraz ograniczenia w dostępie do funkcjonalności dostarczanej przez platformę iPhone mogą z czasem zacząć działać na jej niekorzyść (znów taktyka walled garden). Narzucone restrykcje mogą zniechęcać do tworzenia aplika-cji, które pozwoliłyby na potraktowanie iPhona jako elementu platfor-my usługowej, a nie jedynie terminala użytkownika. Dostrzegł to i wy-korzystał Google, postanawiając udostępnić swoją platformę mobil-ną Android na zasadach kodu otwartego. Użyta licencja Apache Licen-se 2.0 pozwala na tworzenie pochodnych komercyjnych. Niestety nie mamy tu do czynienia z produktem w pełni otwartym, ale na pewno jest to krok w tę stronę.

PodsumowanieW dziedzinie technologii mobilnej sukces jest jedynie w części po-chodną zapotrzebowania. Domena ta to arena interdyscyplinarnej gry na styku interesów biznesu, społeczeństw i polityki. W tym kon-kretnym przypadku nie zawsze jest to jednak hamulec rozwoju. Nie-rzadko można dostrzec zastosowania, których pozytywne skutki bę-dą mieć społeczny charakter, globalny zasięg i długofalowe skutki. Osiągnięcie pożądanych efektów wymaga współdziałania znacznie większej ilości partnerów niż w przypadku innych dziedzin. Niemniej, potencjalne zyski ze stosowania technologii mobilnych sprawiają, że dostarczane przez nie alternatywne rozwiązania są warte co najmniej rozważenia w praktycznie każdym przejawie działalności.

Artykuł wyraża prywatne poglądy autora, które niekoniecznie odzwierciedlają stanowisko Silicon & Software Systems Ltd. (S3)

ARKADIUSZ MERTAAutor od 11 lat zajmuje się zagadnieniami projektowania i realizacji opro-gramowania. Aktualnie jest pracownikiem Silicon & Software Systems Pol-ska zatrudnionym na stanowisku menadżera projektów.Kontakt z autorem: [email protected]

Page 217: SDJ Extra 34 Biblia

Felieton

www.sdjournal.org 217

Według Słownika Języka Polskiego, termin mobilny ozna-cza: takiego, który łatwo daje się wprawić w ruch, kogoś zdolnego do sprawnego, elastycznego działania oraz

często zmieniającego miejsce pobytu lub miejsce pracy. Wszyst-kie te trzy określenia znajdują swoje odzwierciedlenie w techno-logiach zwanych mobilnymi. Są one ukierunkowane na usuwanie barier ograniczających możliwość wykonywania pracy (lub dostę-pu do informacji) niezależnie od miejsca czy posiadanego urzą-dzenia dostępowego. Elastyczność uzyskiwana w ten sposób po-zwala na zwiększenie zasięgu i efektywności prowadzonych dzia-łań. Bezpośrednio przekłada się to na zwiększenie zysków.

Rozważając technologie mobilne, ograniczamy się zazwyczaj do doraźnych i przyszłych kwestii technicznych. Szukamy sposo-bów wykorzystania istniejących rozwiązań do stawianych przed na-mi wyzwań. Szczególnie staramy się przewidzieć przyszłe potrzeby, które wyznaczą kierunki rozwoju rynków. Takie myślenie o przyszło-ści jest konieczne, aby zabezpieczyć ciągłość naszego przedsięwzię-cia. Proces ten polega na analizie wielu czynników i jest tyle skom-plikowany, co dający różne rezultaty (co widać w wynikach finanso-wych spółek). Pewne wskazówki dla niego można uzyskać, szukając powodów popularności, ekspansji technologii mobilnych. W tym celu należy dla odmiany spojrzeć wstecz i niekoniecznie na kwestie związane bezpośrednio z techniką.

Zanim ktokolwiek pomyślał o szerokopasmowym dostępie do In-ternetu w telefonie komórkowym, zanim powstała pierwsza komór-ka, ba – zanim wykonano pierwszą rozmowę telefoniczną – ludzie przemieszczali się. Robią to nadal i będą to robić zawsze. Różne są te-go motywy. W krajach rozwijających się dominuje chęć poprawy wa-runków życia, rozwiniętych – ciekawość (turystyka), zniewolonych – przekonania polityczne, wyznaniowych – religia. Swoboda prze-pływu osób, towarów, kapitału i usług to dla nas podstawowe prze-jawy wolności. Sugeruje to, że w tym konkretnym wypadku prasta-ry spór o pierwszeństwo jajka przed kurą (czy też odwrotnie) został rozstrzygnięty. Przemieszczanie się leży w naszej naturze. Potrzebuje-my tej swobody, żeby czuć się wolnymi i mającymi wpływ na własne życie. Potrzebujemy również możliwości stowarzyszania się (afiliacji), poznawania, informowania, zabawy, ale i bezpieczeństwa czy naby-wania. Stworzyliśmy zatem odpowiednie technologie, które pozwa-lają na zaspokojenie takich potrzeb. Można stąd uznać, że istota po-pularności i rozwoju technologii mobilnych płynie z naszej głębi i od-zwierciedla naturalne potrzeby czy zachcianki.

Z drugiej jednak strony, rozwój społeczeństw wymaga ciągłego podnoszenia efektywności pracy. Coraz bardziej wyrafinowane ma-szyny sprawiają, że siła fizyczna czy ilość pracowników tracą na zna-czeniu. Liczy się sprawność w pozyskiwaniu i interpretacji danych. Dostęp do nich jest konieczny nie tylko w biurze, ale coraz częściej 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-cza już budynku czy biurka, ale miejsce, w którym praca jest faktycz-nie wykonywana, gdzie tworzy się pieniądz. W imię lepszego jutra, społeczeństwa są kreowane na informacyjne. Jako takie nie mogą wyrzec się swojego podstawowego nośnika: dostępu do informacji.

Może to więc kura była pierwsza? Może technologie mobilne są marchewką na miarę XXI wieku? A zachłyśnięcie się nimi wcale nie wynika z naszych potrzeb. Może to jedynie sprytna manipulacja mająca na celu osiąganie coraz lepszych wyników, ograniczając puste przebiegi? Jeżeli przyjmiemy taką spiskową tezę, to również ilość potrzeb zaspokajanych przez komórkę wydaje się pułapką. W ten sposób bowiem telefon jawi się jako narzędzie uniwersal-ne, bez którego nie jesteśmy się w stanie obejść. Wtedy nawet nie-wesoły push-mail z firmy w piątek wieczorem czy telefon od szefa podczas weekendowego grilla da się jakoś przeżyć.

Przedstawione powyżej źródła popularności technik mobilnych przekładają się na dość szeroki wachlarz potencjalnych zastosowań. Wydaje się, że na znaczeniu będą zyskiwać usługi stymulujące uczu-cie wolności (np. szerokopasmowy dostęp, współdzielenie zasobów) i bezpieczeństwa (np. lokalizacyjne, medyczne), a jednocześnie po-zwalające na pozostanie w kontakcie z resztą społeczności (np. mo-bilne portale społecznościowe). Niełatwo będzie nam zrezygnować z takich udogodnień.

Z drugiej strony konieczne jest wspomaganie nowych modeli biz-nesowych pozwalających na zaangażowanie większej ilości podmio-tów – dostawców produktów i usług, oraz ściślejsze powiązanie ich z abonentami. Komórka stanie się przedmiotem zainteresowania za-równo operatorów jak i dostawców treści czy reklamodawców. W tym celu telefon musi ewoluować w kierunku terminala, który umożliwi dostarczanie usług konwergentnych: głosowych oraz przesyłu da-nych. Informacja, nawet ta zajmująca sporo miejsca czy transmitowa-na na żywo, musi stać się lepiej osiągalna.

Technologie mobilne to jednak trudny biznes, w którym sukces jest uzależniony od kapryśnych gustów użytkowników. Operatorzy kon-centrują się bardziej na walce o nowego klienta niż na utrzymaniu już posiadanych. Ostra konkurencja obniża różnice cenowe, co do-datkowo sprzyja migracji klientów. Jedna promocja czy udany mo-del terminala mogą wywrócić układ sił na rynku. Wraz z populary-zacją dostępu do Internetu przez komórkę, operatorzy mogą stra-cić część kontroli nad terminalami. Podobnie nadmierne wykorzysty-wanie technologii mobilnych w imię zwiększenia efektywności, może w końcu wywołać uczucie naruszania sfery prywatności pracownika. To z kolei prowadzi do permanentnego stresu, którego objawami są znużenie, zniechęcenie czy bierność. Osiągnięty efekt będzie dokład-nie odwrotny do zamierzonego.

Dokąd zmierzają technologie mobilne? Ilość potencjalnych obszarów aplikacji stale się zwiększa, podobnie zainteresowanie nimi. Postęp w tej dziedzinie jest błyskawiczny. Pozostaje pytanie: czy ten ruch ma jakiś konkretny cel, czy jego sukces upatrywany jest w … samym ruchu?

Artykuł wyraża prywatne poglądy autora, które niekoniecznie odzwierciedlają stanowisko Silicon & Software Systems Ltd. (S3)

Ruch jest wszystkim [cel niczym]Co napędza rozwój technologi mobilnych? Czy to nasze potrzeby, czy potrzeba zwiększenia naszej efektywności.

ARKADIUSZ MERTAAutor od 11 lat zajmuje się zagadnieniami projektowania i realizacji opro-gramowania. Aktualnie jest pracownikiem Silicon & Software Systems Pol-ska zatrudnionym na stanowisku menadżera projektów.Kontakt z autorem: [email protected]

Page 218: SDJ Extra 34 Biblia

v 1

Zamówienie prenumeraty

Imię i nazwisko ...............................................................................

Nazwa firmy.....................................................................................

Dokładny adres ..............................................................................

.........................................................................................................

Telefon ............................................................................................

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

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

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

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

TytułIlość

nume-rów

Ilość zama-

wianych prenu-merat

Od numeru pisma

lub mie-siąca

Cena

Software Develo-per’s Journal Extra

4 199PLN

□ automatyczne przedłużenie prenumeraty

Software Developer’s Journal Extra „Biblia Progra-misty” jest kwartalnikiem głównie dla programistów, którzy liczą, że dostarczymy im gotowe rozwiąza-nia, oszczędzając im czasu i pracy. Jesteśmy czyta-ni przez tych, którzy chcą być na bieżąco informowa-ni o najnowszych osiągnięciach w dziedzinie IT i nie chcą, żeby jakiekolwiek istotne wydarzenia umknęły ich uwadze. Aby zadowolić naszych czytelników, pre-zentujemy zarówno najnowsze rozwiązania, jaki star-sze, sprawdzone technologie.

,-Prosimy wypełniać czytelnie i przesyłać faksem na numer: 00 48 22 877 20 70lub listownie na adres: EuroPress Polska Sp. z o.o.ul. Jana Kazimierza 46/5401-248 WarszawaPolskaE-Mail: [email protected]

Przyjmujemy też zamównienia telefoniczne:00 48 22 877 20 80

Jeżeli chcesz się dowiedzieć o formach płatności, wejdź na stronę:www.europress.pl lub napisz na e-mail: [email protected]

Obsługa prenumeraty

1. Telefon+48 22 877 20 80 2. Fax+48 22 877 20 70 2. [email protected]

3. AdresEuroPress Polska Sp. z o.o.ul. Jana Kazimierza 46/5401-248 Warszawa

Roczna prenumerata tylko 199

UWAGA!

Zmiana danych kontaktowych

Zadzwoń+48 22 877 20 80

lubzamów

mailowo!

Page 219: SDJ Extra 34 Biblia

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

Page 220: SDJ Extra 34 Biblia