02 października 2011

Zapiski z czwartej części "Hello, Android" - "Next Generation" i piątej "Appendixes"

To już miesiąc od kiedy zadeklarowałem poświęcić swój czas na poznanie Androida i na sam koniec androidowego września, który zacząłem od wpisu Wrześniowy Android, skończyłem czytanie książki "Hello, Android", wydanie 3.

W tym wpisie przedstawię zapiski z czwartej części książki - "Next Generation" oraz części piątej "Appendixes".

Zapiski z poprzednich części książki "Hello, Android" znajdziesz we wpisach:

Ekrany dotykowe ze wsparciem wielopalcowych gestów


Tak, przynaję, że użycie słowa "wielopalcowych" (zamiast "wielodotykowych") może budzić swego rodzaju "podziw", że mogło mi to przyjść w ogóle do głowy, ale preferuję takie tłumaczenie, bo oddaje ich źródło powstania przez użycie wielu palców, co niekoniecznie może oznaczać, że dotknięć, które pojedynczy palec również może być źródłem, np. podwójne puknięcie. Gdyby ktoś jednak zechciał uzasadnić mój błąd, byłbym niezmiernie wdzięczny.

Rozdział 11. "Multi-touch" omawia zdarzenia generowane przez więcej niż jeden palec.

Wyróżniono gesty związane z jednym palcem: stuknięcie (ang. tap) oraz przesuwanie (ang. drag), np. przy przewijaniu.

Popularność iPhone wymusiła na dostawcach urządzeń z Androidem wsparcie gestów wielopalcowych, np. skalowanie (ang. pinch zoom), tj. zbliżanie lub oddalanie dwóch palców na ekranie w celu zmniejszenia lub zwiększenia treści pod nimi.

Nowa klasa android.view.ScaleGestureDetector do skalowania w Android 2.2.

Autor przedstawia stworzenie prostej przeglądarki obrazków do wyjaśnienia obsługi gestów.

Pojawiły się klasy android.view.MotionEvent, android.view.View.OnTouchListener, android.widget.ImageView, android.util.FloatMath, android.widget.FrameLayout z @drawable oraz android:scaleType="matrix" w pliku układu. Ich znajomość zdaje się być kluczowa do napisania własnych aplikacji, które udostępniają tego rodzaju interakcję z użytkownikiem.

@drawable wskazuje na dowolny JPG lub PNG z res, pewnie najczęściej z podkatalogu drawable-nodpi (trochę więcej o tym poniżej).

android:scaleType="matrix" - macierz do zmiany położenia (przesuwanie) i skalowania obrazka - za pomocą macierzy możemy wyrazić dowolne przekształcenie (i znowu wracam do macierzy, które niekoniecznie przypadły mi do gustu na studiach).

Pojawiają się również klasy android.graphics.Matrix (głównie metody Matrix.set() oraz Matrix.postTranslate()), android.graphics.PointF oraz użycie metody ImageView.setImageMatrix(matrix). Odnotowane do dalszego rozpoznania.

W trakcie czytania tego rozdziału, doświadczam tego błogiego stanu, w którym poznawanie urządzenia odbywa się przez poznawanie możliwości jego oprogramowywania, czyli kontrolowania na niższym niż na poziomie końcowego użytkownika (aczkolwiek wciąż nie tak nisko jak na poziomie systemu operacyjnego, poniżej poziomu Dalvik VM). Jestem zdania, że to właśnie programiści mają tę przewagę nad końcowymi użytkownikami lub administratorami (którzy również są swego rodzaju końcowymi użytkownikami produktu), że wiedzą jak i dlaczego dana cecha tak działa, a nie tylko jak.

W tym samym momencie, mój syn przerabia na matematyce (3 klasa gimnazjum) pojęcie podobieństwa trójkątów, w którym termin "skala" jest fundamentalne. Mimo, że takie proste na pierwszy rzut oka, to i tak zaskoczyła mnie łatwość wyliczania skali powiększenia/pomniejszenia obrazka za pomocą przyrównania wyjściowej odległości do bieżącej (nowej). To są te chwile, w których stwierdzam, że trzeba było uważać na lekcjach matematyki, kiedy i tak należało siedzieć w sali i trawić, co podaje wykładowca. Zakładam przy tym, że Ci, którzy uczą, robią to właściwie i z należytym zaangażowaniem.

UWAGA: Nie wszystkie urządzenia androidowe mają wsparcie sprzętowe dla obsługi równań z użyciem typu float i częste ich użycie może znacząco wpływać na prędkość działania aplikacji.

UWAGA: W książce zwrócono również uwagę na odśmiecacz (GC) w Javie i aby niepotrzebnie nie tworzyć nowych obiektów, które za moment trafią do kosza i wydłuży działanie GC, wykorzystuje się ten sam obiekt zmieniając jego stan. Tutaj Clojure, który wymusza programowanie z krótkotrwałymi obiektami (cecha języka funkcyjnego na JVM, w którym nacisk kładzie się na niezmienność struktur danych, aby tym samym ułatwić programowanie współbieżne) nie sprawdzi się. Ale czyż umiejętność wyboru właściwego narzędzia do problemu nie jest podstawowym wymaganiem w naszej branży?

android:theme="@android:style/Theme.NoTitleBar.Fullscreen" (mała litera S w Fullscreen!) - widok zajmuje całą dostępną przestrzeń bez paska tytułowego lub postępu na górze.

Obsłużenie zdarzenia wymaga, aby metoda przechwytująca je zwróciła true, np. implementacja onTouch(View v, MotionEvent event) { return true; }

Emulator na niewiele się zda, ponieważ nie wspiera gestów wielopalcowych. Nie pozostaje nic innego jak zaopatrzyć się w prawdziwe urządzenie i testowanie aplikacji bezpośrednio na nim.
Obsługa zdarzeń dot. gestów to jak przetwarzanie całej masy komunikatów i niektóre należy filtrować, aby czas spędzony na ich obsłudze nie wpływał miażdżąco na działanie aplikacji.

Teoretycznie Android API wspiera 256 palców, ale większość urządzeń kończy swoje wsparcie na 2 (tutaj autor powołuje się na osobników z filmu Faceci w Czerni, w którym istnienie istot z więcej niż 10 palcami u rąk nie jest czymś nadzwyczajnym). Nie pomyślałem o tym wcześniej, ale biorąc pod uwagę, jak wiele różnych modeli urządzeń z Androidem istnieje na rynku, można sobie śmiało wyobrazić takie, które obsługują wręcz wiele osób, np. przewodniki interaktywne w muzeach, halach, konferencjach, itp. (już w mojej głowie tworzy się obraz dzieciaków pochylonych nad dotykowym ekranem, który prowadzi przez muzeum, czy inny obiekt, które na różne sposoby weryfikują poprawność wsparcia wielu palców).

Autor poleca stronę http://gestureworks.com, aby przekonać się, jak wiele gestów jest już używanych w aplikacjach. Jako użytkownik Mac OS X na MacBook Pro korzystam z gestów z 4 palcami (góra/dół do pokazania wszystkich okien) i uważam je za co najmniej interesujące (aczkolwiek przyzwyczajenie się do nich, zwane nauczeniem się ich, trało dłuższą chwilę).

Widgety na ekranie początkowym - stronie domowej


Widgety w Androidzie to niewielkie (funkcjonalnie) aplikacje uruchamiane bezpośrednio na pulpicie, np. zegarek, pogoda, wyświetlanie obrazka, itp.

Nie ma wsparcia do tworzenia widgetów w Eclipse ADT. Tworzymy projekt androidowy ze zmianami - nie jest potrzebna początkowa aktywność (activity), a w AndroidManifest.xml używamy <receiver> z <intent-filter> zamiast <activity>. Układ elementów graficznych określamy w pliku XML wskazany przez <meta-data> w AndroidManifest.xml.

Przeczytałem o NinePitch, który jest rozszerzalnym obrazkiem PNG, często używanym jako tło dopasowujących się przycisków. Do ich tworzenia korzystamy z narzędzia draw9patch (w Android SDK w katalogu tools). Utworzony plik musi mieć rozszerzenie 9.png i być w res/drawable.

Klasa bazowa widgetów android.appwidget.AppWidgetProvider.

Uruchomienie widgeta podobne do aplikacji z tą różnicą, że po wgraniu musimy jawnie uaktywnić przez umieszcznie na pulpicie. Widget może odświeżać swoją zawartość. Wystarczy zadeklarować intent APPWIDGET_UPDATE i pojawienie się zdarzenia wzbudza onUpdate naszego widgeta. Częstotliwość aktualizacji określana jest parametrem android:updatePeriodMillis w widget.xml wskazywanym przez AndroidManifest.xml. Uwaga na częstość odświeżania, bo bateria urządzenia bardzo niewielka i na niewiele starczy.

Więcej na temat widgetów w dokumentacji App Widgets.

Dynamiczne tapety


Autor zaprezentował utworzenie projektu z użyciem OpenGL. Ponownie zabawa z AndroidManifest.xml - użycie znacznika <service>, ponownie intent-filter z meta-data z android:name="android.service.wallpaper".

Po raz pierwszy przeczytałem o usługach (ang. service) uruchamianych w tle, które reagują na zdarzenia z urządzenia. Klasa macierzysta android.app.Service i po trosze przypomina Activity - mamy onCreate() i onDestroy(). Są jeszcze onStartCommand() przy uruchomieniu usługi.

Kolejny raz, kiedy przychodzi dokładniejsze rozpoznanie tematu, autor skraca temat wskazując na dokumentację.

Tworzenie dynamicznych tapet sprowadza się do rozszerzenia klasy android.service.wallpaper.WallpaperService. Poznaję również nieodłączną klasę tapet android.service.wallpaper.WallpaperService.Engine.

Czasami autor przesadza z wprowadzaniem czytelnika w arkana programowania aplikacji na Androida i niepotrzebnie tłumaczy, jak programować w Javie, np. konstrukcję klas wewnętrznych oraz wykorzystanie final dla pól poza ich ciałem, aby były widoczne. Później jeszcze przydługawe omówienie generowania domyślnych ciał metod klasy nadrzędnej oraz rozwiązywania problemów braku właściwych importów z Quick Fix lub Organize Imports w Eclipse. Niejednokrotnie byłem zaskoczony, że w ogóle przedstawia się takie tematy.

Przy okazji poznawania tapet w Androidzie mogłem poczytać o klasie java.util.concurrent.Executor. Podobnie jak w Swingu należy bacznie rozplanować zrównoleglanie operacji, szczególnie tych, które modyfikują elementy graficzne. W przykładzie korzysta się z java.util.concurrent.Executors.newSingleThreadExecutor().

Widzę u siebie duże braki w temacie zmian w Java SE 5 i nowszych w obszarze java.util.concurrent. Kolejna książka musi być właśnie o tym. Sugestie mile widziane (poza Java Concurrency in Practice Brian's Goetz'a, którą już niejednokrotnie zaczynałem, ale chyba dopiero teraz dojrzałem do zmierzenia się z nią).

Testowanie


Należy testować. Każdy to wie i trochę niepoważnie przypominać o tym. Jeśli testowanie w Javie stanowiło swego rodzaju wyzwanie, to zmiana środowiska na androidowe będzie nakładała dodatkowe ograniczenia i wymagania. Wiele różnych rodzajów urządzeń wymaga stworzenia wielu AVD w Eclipse. Należy testować na różnych wymiarach ekranów i rozdzielczości. Nie zapominamy o testowaniu w różnych orientacjach - poziomie i pionie (w Emulatorze Ctrl+F11 lub 7 i 9 na klawiaturze).

Podczas uruchamiania określamy w zakładce Target, Deployment Target Selection Mode na Manual i wybieramy odpowiednie urządzenie. Wskazanie tego właściwego urządzenia odbywa się przez AndroidManifest.xml przez znacznik <uses-sdk> z elementami android:minSdkVersion oraz android:targetSdkVersion. W ten sposób określamy minimalne wymagania wersji Androida ze wskazaniem na zalecaną wersję.

Poznaję wyjątek java.lang.VerifyError, który pojawia się, kiedykolwiek wywołujemy metodę, która nie istnieje podczas uruchomienia (a istniała podczas kompilacji). Dosyć popularny wyjątek, kiedy zapomnimy o różnych wersjach Androida i różnicach między nimi w klasach i ich metodach. Rozwiązaniem było zastosowanie wzorców Delegator i Fabryka. Jak widać, przy okazji poznawania Androida można poznać również wzorce. Krótko i rzeczowo - mi się podobało.

Następnie autor przedstawia sposoby na poznawanie Androida od strony jego błędów. Warto od czasu do czasu zajrzeć do kodów źródłowych Android API - platform/frameworks/base.git. Zmiany w klasach opatrzone są zwykle komentarzem ze wskazaniem na błędy, które zapoczątkowały je.

Jest jeszcze trochę o podkatalogach w res dla obrazków na różne rozdzielczości ekranów - drawable-hdpi, drawable-mdpi, drawable-ldpi lub drawable-nodpi. Dowiedziałem się również o różnych kwalifikatorach w nazwach katalogów, dla różnych wersji językowych, rozdzielczości, wymiarach ekranu, położeniu (orientacji), wersji SDK i in. Więcej niż jeden kwalifikator oddziela się w nazwie myślnikiem.

Na koniec wzmianka o android:installLocation w AndroidManifest.xml, który wskazuje na możliwość instalowania aplikacji na karcie SD (zamiast bezpośrednio na urządzeniu). Dwie dozwolone wartości auto i preferExternal. Brak wartości oznacza instalację na urządzeniu. Google zaleca, aby nie instalować na karcie SD aplikacji korzystających z pewnych funkcjonalności Androida, np. tapety, usługi, widgety.

Ponownie, na koniec rozdziału zalecenie, aby zapoznać się z oficjalną dokumentacją, aby poznać więcej. Normalka.

Publikowanie w Android Market


Na zakończenie części czwartej rozdział o udostępnianiu aplikacji, czyli publikowaniu aplikacji w Android Market. Autor przeprowadza przez cały proces, począwszy od wyboru odpowiedniego i unikatowego pakietu (patrz AndroidManifest.xml), testowania, podpisywania i wrzuceniu do Android Market.

Raz wybrany pakiet aplikacji nie podlega zmianie (bez odinstalowania aplikacji). Niech to będzie pakiet główny naszych klas javowych, np. pl.japila.aplikacja (tzn. ta jest zarezerwowana dla moich aplikacji, ale rozumiem, że Ty rozumiesz, że chodzi o odwrotną notację z domenami w tle, jak w Javie).

Do zapamiętania: "Make your program do one thing well, rather than a lot of things poorly." 

Poznaję android:versionCode i android:versionName z AndroidManifest.xml.

Warto zapoznać się z User Interface Guidelines.

Później omówienie apk i podpisania z użyciem keytool, jarsigner lub oprzeć się na Eclipse ADT - Android Tools > Export Signed Application Package. Więcej na Signing Your Applications. Na koniec otrzymujemy podpisany plik apk. Z plikiem apk udajemy się do Android Market i po opłaceniu jednorazowej, podobno niewielkiej, opłaty, wybieramy Upload Application. Wypełniamy formularz, w którym wyłączamy Copy Protection (podobno jedynie irytuje użytkowników i wcale nie zabezpiecza), wybieramy All Current and Future Countries w Locations i nie podajemy numeru telefonu - użytkownicy skorzystają z niego w najmniej odpowiednim momencie (!) Publish i aplikacja jest dostępna od ręki w Markecie.

Aktualizacja aplikacji wymaga zmiany android:versionCode o jeden w górę i android:versionName w AndroidManifest.xml. Publish i zmiana dostępna. Sugeruje się częste aktualizacje - utrzymuje (często błędne?) przekonanie o ciągłym wsparciu aplikacji (co nie wprost może być faktycznie prawdą, bo w końcu nowe funkcje są wprowadzane) oraz nasza aplikacja znajduje się w Recently Updated, co podnosi widoczność aplikacji.

Warto rozważyć opublikowanie aplikacji w dwóch wersjach - darmowej (light) oraz płatnej (pro). Płatną aplikację możemy zmienić na darmową, ale nie na odwrót.

Obecna wersja Android Market nie daje możliwości zakupienia własnej aplikacji.

Dodatek: "Różnice programowe" w Android API vs Java API


Pisanie aplikacji na Androida sprowadza się w dużej mierze do umiejętności posługiwania się Java SE 5 API. Istnieje jednak kilka różnic wynikających ze specyficznego środowiska, na którym będą działały aplikacje mobilne.

Zanim aplikacja trafi na urządzenie androidowe, aplikacja przechodzi proces kompilacji, jak to ma miejsce w typowych aplikacjach javowych, z tą różnicą, że bajtkod jest dodatkowo tłumaczony na instrukcje Dalvik VM.

Wszystkie konstrukcje języka Java SE 5 są wspierane (nie dotyczy to jednak API, a wyłącznie samego języka Java). W ten sposób możliwe jest korzystanie z aplikacji javowych, które nie były pierwotnie pisane na Androida (z dokładnością do używanych bibliotek i API, które mogą być niedostępne - o tym za moment).

Uwaga na użycie typów prostych float oraz double. Niektóre urządzenia mogą nie posiadać wsparcia sprzętowego dla operacji zmiennoprzecinkowych i przez to działanie aplikacji będzie znacznie opóźniane przez obliczenia aplikacyjne.

Wątki są wspierane przez przydzielanie kawałków czasu na uruchomienie jednego, tzw. time slicing. Stąd korzystanie z wątków powinniśmy sprowdzić do użycia jednego bądź maksymalnie dwóch. Pierwszy będzie dedykowany do obsługi interakcji z użytkownikiem, a drugi dla długotrwałych operacji, np. obliczenia, operacje I/O.

Dalvik VM wspiera słowo kluczowe synchronized oraz Object.wait(), Object.notify(), Object.notifyAll() oraz (preferowane jako konstrukcje wyższego poziomu) pakiet java.util.concurrent.

Na urządzeniach mobilnych ograniczanych dostępnymi zasobami tym bardziej ważne jest zamykanie/zwalnianie zasobów tak szybko, jak to możliwe, korzystając z oferowanych przez nie metod close() lub terminate() (super byłoby, gdyby można było stosować try-with-resources z Java 7).

Android wspiera większą część Java SE 5 API, poza tymi, których użycie i tak nie miałoby sensu. Wspierane:
  • java.awt.font
  • java.beans
  • java.io
  • java.lang - poza java.lang.management
  • java.math
  • java.net
  • java.nio
  • java.security
  • java.sql
  • java.text
  • java.util - wraz z java.util.concurrent - dopiero teraz zauważam, że oferowane w tym pakiecie klasy są rzeczywiście uproszczeniami i powinny być oferowane w Javie od pierwszego jej dnia publikacji
  • java.crypto
  • java.microedition.khronos
  • javax.net
  • javax.security - poza kilkoma podpakietami - auth.kerberos, auth.spi, sasl
  • javax.sql - poza podpakietem rowset
  • javax.xml.parsers
  • org.w3c.dom - bez podpakietów
  • org.xml.sax
Jakkolwiek udostępniono JDBC API, to zaleca się korzystanie z android.database do dostępu do SQLite (preferowanej bazy danych SQL na Androidzie) Poza standardowymi bibliotekami Java SE 5 API mamy jeszcze org.apache.http, org.json, org.xml.sax i org.xmlpull.v1. Na tym kończę lekturę książki. Jestem zachwycony, że mogłem ją przeczytać, mimo że często doprowadzała mnie do pasji spłycając analizę problemu do "Po więcej należy udać się do oficjalnej dokumentacji Androida". Recenzja wkrótce.