Projektowanie z myślą o spójności

Nawet jeśli Twoja aplikacja jest szybka i responsywna, niektóre decyzje projektowe mogą nadal powodować problemy u użytkowników – z powodu nieplanowanych interakcji z innymi aplikacjami lub oknami, niezamierzonej utraty danych, niezamierzonego blokowania itp. Aby uniknąć tych problemów, warto poznać kontekst, w którym działają Twoje aplikacje, i interakcje z systemem, które mogą na nie wpływać. Krótko mówiąc, warto opracować aplikację, która bezproblemowo współpracuje z systemem i innymi aplikacjami.

Często występujący problem z płynnością polega na tym, że proces aplikacji w tle – na przykład usługa lub odbiornik – wyświetla okno dialogowe w odpowiedzi na jakieś zdarzenie. Może się to wydawać niegroźne, zwłaszcza gdy tworzysz i testujesz aplikację w izolacji, używając emulatora. Jeśli jednak aplikacja zostanie uruchomiona na rzeczywistym urządzeniu, może ona nie być w niej aktywna w momencie, gdy proces w tle wyświetla to okno. Może się więc zdarzyć, że aplikacja wyświetli okno dialogowe z aktywną aplikacją lub przejmie fokus na bieżącą aplikację i przed wszystkim, co użytkownik robi (np. dzwoni przez telefon). Takie działanie nie zadziała w Twojej aplikacji ani w przypadku użytkownika.

Aby uniknąć tych problemów, aplikacja powinna używać odpowiedniej funkcji systemu do powiadamiania użytkownika – klas Notification. Dzięki powiadomieniom aplikacja może zasygnalizować użytkownikowi miejsce zdarzenia. W tym celu wyświetla ikonę na pasku stanu, zamiast rozpraszać użytkownika i przeszkadzać mu.

Innym przykładem problemu z płynnością jest nieumyślna utrata danych o stanie lub danych użytkownika przez aktywność z powodu niepoprawnej implementacji funkcji onPause() i innych metod cyklu życia. Lub, jeśli aplikacja udostępnia dane przeznaczone do wykorzystania przez inne aplikacje, należy je udostępniać za pomocą komponentu ContentProvider, a nie na przykład używać gotowego do odczytu nieprzetworzonego pliku lub bazy danych.

Wspólną cechą tych przykładów jest sprawna współpraca z systemem i innymi aplikacjami. System Android został zaprojektowany tak, aby traktować aplikacje jako rodzaj federacji luźno powiązanych ze sobą komponentów, a nie jako czarnych bloków kodu. Dzięki temu deweloper może zobaczyć cały system jako jeszcze większą federację tych komponentów. Dzięki temu możesz przeprowadzić bezproblemową integrację z innymi aplikacjami i zaprojektować własny kod, który zwróci Ci tę korzyść.

W tym dokumencie opisujemy typowe problemy ze zgodnością z wymaganiami i omawiamy sposoby ich unikania.

Nie usuwaj danych

Zawsze pamiętaj, że Android to platforma mobilna. Może się to wydawać oczywiste, ale pamiętaj, że w każdej chwili może pojawić się inna aktywność (np. aplikacja „Przychodzące połączenie telefoniczne”). Spowoduje to uruchomienie metod onSaveInstanceState() i onPause(), a najprawdopodobniej spowoduje zamknięcie aplikacji.

Jeśli w chwili, gdy pojawiła się inna aktywność, użytkownik edytował dane w Twojej aplikacji, po jej zamknięciu aplikacja prawdopodobnie utraci te dane. Oczywiście najpierw zapiszesz postępy pracy. W tym celu należy zastosować metodę „Android” – aplikacje na Androida, które akceptują lub edytują dane wejściowe, powinny zastąpić metodę onSaveInstanceState() i zapisać swój stan w odpowiedniej formie. Gdy użytkownik ponownie otworzy aplikację, powinien mieć możliwość odzyskania danych.

Klasycznym przykładem dobrego zastosowania takiego działania jest aplikacja pocztowa. Jeśli użytkownik pisał e-maila w momencie uruchomienia innego działania, aplikacja powinna zapisać ten e-mail jako wersję roboczą.

Nie ujawniaj nieprzetworzonych danych

Wyobraź sobie, że nie możesz chodzić w bieliźnie. Twoje dane również nie powinny. Chociaż istnieje możliwość udostępnienia określonych rodzajów aplikacji całemu światu, zazwyczaj nie jest to najlepszy pomysł. Ujawnienie nieprzetworzonych danych wymaga, aby inne aplikacje rozumieły format danych. Jeśli zmienisz ten format, spowoduje to uszkodzenie wszystkich innych aplikacji, które nie są aktualizowane w podobny sposób.

Metoda „Android Way” polega na utworzeniu komponentu ContentProvider, który umożliwi udostępnianie danych innym aplikacjom za pomocą przejrzystego, dobrze przemyślanego i łatwego w obsłudze interfejsu API. Używanie ContentProvider przypomina wstawianie interfejsu w języku Java w celu podziału i skomponowania 2 ściśle powiązanych fragmentów kodu. Oznacza to, że będzie można zmodyfikować wewnętrzny format danych bez zmiany interfejsu udostępnianego przez ContentProvider – bez wpływu na inne aplikacje.

Nie przeszkadzaj użytkownikowi

Jeśli użytkownik uruchamia jakąś aplikację (np. aplikację Telefon w trakcie rozmowy), można się spodziewać, że zrobił to celowo. Dlatego należy unikać ich pojawiania się poza bezpośrednią odpowiedzią na dane wejściowe użytkownika z bieżącego działania.

Oznacza to, że nie wywoływaj funkcji startActivity() z funkcji BroadcastReceivedrs ani usług działających w tle. Spowoduje to przerwanie uruchomionej aplikacji i zniechęcenie użytkownika. Co gorsza, Twoja aktywność może zostać uznana za „bandytę korzystającą z naciśnięcia klawiszy” i otrzymywać informacje, które użytkownik wpisał w trakcie poprzedniego działania. W zależności od tego, co robisz, może to być zła wiadomość.

Zamiast tworzyć interfejsy aktywności bezpośrednio w tle, do ustawiania powiadomień lepiej jest używać menedżera powiadomień. Zostaną one wyświetlone na pasku stanu, a użytkownik będzie mógł je w dowolnej chwili kliknąć, aby sprawdzić, co musi zawierać Twoja aplikacja.

(Pamiętaj, że nie dotyczy to sytuacji, w których Twoja aktywność jest już na pierwszym planie – w takim przypadku użytkownik oczekuje, że w odpowiedzi na wprowadzone informacje zobaczy Twoją kolejną aktywność).

Masz dużo do zrobienia? Zrób to w wątku

Jeśli Twoja aplikacja musi wykonywać drogie lub długotrwałe obliczenia, najlepiej przenieś ją do wątku. Dzięki temu użytkownik nie będzie mógł wyświetlić okna „Aplikacja nie odpowiada”, a jego ostatecznym wynikiem będzie straszna śmiercia aplikacji.

Domyślnie cały kod aktywności oraz wszystkie jej widoki działają w tym samym wątku. Jest to ten sam wątek, który obsługuje też zdarzenia interfejsu. Na przykład: gdy użytkownik naciśnie klawisz, do kolejki głównego wątku aktywności zostanie dodane zdarzenie „keydown”. System obsługi zdarzeń musi szybko usunąć to zdarzenie i je szybko obsłużyć. Jeśli tak się nie stanie, po kilku sekundach system uzna, że aplikacja jest zawieszona i proponuje jej zamknięcie za użytkownika.

Jeśli masz kod działający od dawna, uruchomienie go w sekcji Aktywność spowoduje uruchomienie go w wątku modułu obsługi zdarzeń, co skutecznie zablokuje moduł obsługi zdarzeń. Spowoduje to opóźnienie przetwarzania danych wejściowych i spowoduje wyświetlenie okien ANR. Aby tego uniknąć, przenieś obliczenia do wątku. Z tego dokumentu Projektowanie pod kątem reagowania dowiesz się, jak to zrobić.

Nie przeciążaj jednego ekranu aktywności

Każda aplikacja, której warto użyć, będzie prawdopodobnie mieć kilka różnych ekranów. Podczas projektowania ekranów interfejsu pamiętaj, aby używać wielu instancji obiektu aktywności.

Zależnie od doświadczenia w programowaniu aktywność możesz interpretować podobnie jak aplet Java, ponieważ jest punktem wejścia dla Twojej aplikacji. Nie jest to jednak całkiem dokładne. Gdy podklasa apletu jest pojedynczym punktem wejścia dla apletu Java, aktywność należy traktować jako jeden z potencjalnie kilku punktów wejścia do aplikacji. Jedyną różnicą między Twoją „główną” aktywnością a innymi rodzajami aktywności jest to, że „główna” aktywność jest po prostu jedyną, która wyraziła zainteresowanie działaniem „android.intent.action.MAIN” w pliku AndroidManifest..xml.

Podczas projektowania aplikacji traktuj ją jak federację obiektów aktywności. Dzięki temu łatwiej będzie Ci później zarządzać kodem, a jako miły efekt uboczny będzie też dobrze współgrać z historią aplikacji i modelem „backstack”.

Rozszerz motywy systemowe

Jeśli chodzi o wygląd i działanie interfejsu, ważne jest, aby ładnie się wpasowywał. Irytują użytkowników aplikacje, które kontrastują z oczekiwanym interfejsem użytkownika. Podczas projektowania UI postaraj się unikać tworzenia własnych aplikacji. Zamiast tego użyj motywu. Możesz zastąpić lub rozszerzyć te części motywu, które są Ci potrzebne, ale przynajmniej zaczynasz od tego samego interfejsu użytkownika co wszystkie inne aplikacje. Więcej informacji znajdziesz w artykule Style i motywy.

Zaprojektuj interfejs tak, aby współpracował z różnymi rozdzielczościami ekranu

Poszczególne urządzenia z Androidem obsługują różne rozdzielczości ekranu. Niektóre z nich będą nawet mogły zmieniać rozdzielczość na bieżąco, np. przełączając się na tryb poziomy. Układy i elementy rysowane powinny być na tyle elastyczne, by wyświetlały się prawidłowo na różnych ekranach urządzeń.

Na szczęście jest to bardzo łatwe. W skrócie: musisz udostępnić różne wersje swojej grafiki (jeśli jakieś stosujesz) o największej rozdzielczości, a następnie zaprojektować układ tak, by uwzględniał różne wymiary. Unikaj np. umieszczania pozycji zakodowanych na stałe, lecz układów względnych. Jeśli tak dużo robisz, system zajmie się resztą, a aplikacja będzie wyglądała świetnie na każdym urządzeniu.

Załóż, że sieć działa wolno

Urządzenia z Androidem mają różne opcje połączenia sieciowego. W przypadku wszystkich usług będzie dostępny określony mechanizm dostępu do danych, ale niektóre będą szybsze od innych. Najniższym wspólnym mianownikiem jest jednak GPRS, czyli usługa transmisji danych inna niż 3G w sieciach GSM. Nawet urządzenia obsługujące 3G będą spędzać dużo czasu w sieciach innych niż 3G, więc powolne sieci pozostaną dostępne przez dłuższy czas.

Dlatego należy kodować aplikacje w taki sposób, aby zminimalizować dostęp do sieci i przepustowość. Nie dopuszczasz do tego, że sieć jest szybka, więc pamiętaj, że zawsze działa wolno. Jeśli Twoi użytkownicy korzystają z szybszych sieci, to świetnie, bo ich obsługa się poprawi. Warto jednak uniknąć odwrotnej sytuacji: aplikacje, których można używać przez pewien czas, ale denerwująco spowalnia pozostałe, w zależności od tego, gdzie w danej chwili znajduje się użytkownik, prawdopodobnie nie są popularne.

Inną zaletą jest to, że bardzo łatwo wpaść w tę pułapkę, jeśli używasz emulatora, ponieważ korzysta on z połączenia sieciowego komputera. To niemal na pewno szybsze niż w przypadku sieci komórkowej, więc np. zmień ustawienia emulatora, aby symulować wolniejsze połączenia sieciowe. Możesz to zrobić w Android Studio przy użyciu Menedżera AVD lub opcji wiersza poleceń podczas uruchamiania emulatora.

Nie zakładaj ekranu dotykowego ani klawiatury

Android obsługuje różne formaty telefonów. To wymyślny sposób sugerowania, że niektóre urządzenia z Androidem mają pełną klawiaturę „QWERTY”, a inne 40-klawiszowe, 12-klawiszowe, a nawet inne. Niektóre urządzenia mają ekrany dotykowe, ale wiele z nich nie ma.

Pamiętaj o tym podczas tworzenia aplikacji. Nie zakładaj żadnych konkretnych układów klawiatury, chyba że zależy Ci na ograniczeniu aplikacji, tak aby można było z niej korzystać tylko na tych urządzeniach.

Oszczędzaj baterię urządzenia

Urządzenie mobilne nie jest zbyt mobilne, jeśli jest stale podłączone do ściany. Urządzenia mobilne są zasilane z baterii, więc im dłużej możemy je naładować, tym bardziej zadowoleni są wszyscy, a zwłaszcza użytkownik. Dwa z największych konsumentów energii baterii to procesor i radio. Dlatego tak ważne jest napisanie aplikacji tak, aby zajmowały się jak najmniejszą pracą i jak najrzadsze korzystanie z sieci.

Minimalizowanie czasu pracy procesora przez aplikację sprowadza się do efektywnego pisania kodu. Aby zminimalizować zużycie energii przez radio, zadbaj o zgodność z obsługą błędów i pobieraj tylko to, czego potrzebujesz. Nie podejmuj na przykład ciągłego ponawiania operacji sieciowych, jeśli któraś z nich się nie uda. Jeśli raz się nie uda, najprawdopodobniej użytkownik nie odbiera sygnału, więc jeśli od razu spróbujesz to zrobić, znowu się nie uda.

Użytkownicy są bardzo sprytni: jeśli Twój program jest głodny, możesz liczyć na to, że doceni to. W tym momencie można tylko mieć pewność, że program nie będzie instalowany zbyt długo.