Flash Sale! Up to 40% off on unlimited courses, tutorials and creative asset downloads Up to 40% off on unlimited assets SAVE NOW
Advertisement
  1. Code
  2. Android SDK
Code

Android. Współbieżność przy użyciu HaMeR

by
Difficulty:IntermediateLength:MediumLanguages:

Polish (Polski) translation by Jakub Biały (you can also view the original English article)

1. Wprowadzenie

Każdy, kto próbuje tworzyć aplikacje na Androida, odkrywa znaczenie współbieżności. Jedynym sposobem tworzenia responsywnych aplikacji, jest unikanie wykonywania długotrwałych operacji na wątku interfejsu użytkownika, na tyle, na ile jest to możliwe, wykonując całą długotrwałą pracę asynchronicznie - przez wątek w tle.

Ze względu na konstrukcję Androida, zarządzanie wątkami korzystając tylko pakietów java.lang.threadjava.util.concurrent może być nieco kłopotliwe. Korzystając z niskopoziomowych pakietów wątkowania Androida musisz pamiętać o nieco mylącej synchronizacji, do uniknięcia sytuacji wyścigu. Na szczęście ludzie z Google wykonali kawał dobrej roboty i stworzyli kilka, naprawdę dobrych narzędzi, które uczynią Twoją pracę o wiele łatwiejszą: AsyncTask, IntentServiceLoaderAsyncQueryHandler i CursorLoader są przydatne, jak i klasy HaMeRa: HandlerMessage, i Runnable. Jest wiele opcji do wyboru, każda z nich ma swoje plusy i minusy.

Wiele już zostało powiedziane o AsyncTask i sporo ludzi używa go jak panaceum na współbieżność Androida. Jest to niezwykle praktyczne rozwiązanie dla krótkich operacji, łatwe w implementacji i prawdopodobnie najpopularniejsze podejście do współbieżności na Androidzie. Jeśli chcesz dowiedzieć się więcej o AsyncTask rzuć okiem na te posty Envato Tuts+.

Jednakże AsyncTask nie powinien być jedynym narzędziem w twoim ekwipunku.

Do długotrwałych operacji, współbieżności złożonych problemów, lub dla zwiększenia wydajności w pewnych sytuacjach powinieneś skorzystać z innego rozwiązania. Jeśli potrzebujesz większej elastyczności lub wydajności niż w przypadku AsyncTask powinieneś skorzystać z frameworku HaMeR (HandlerMessage & Runnable). W tym poradniku będziemy poznawać framework HaMeR, jeden z najbardziej zaawansowanych modeli współbieżności dostępnych na Androidzie, nauczymy się kiedy, oraz jak go używać. W dalszej części poradnika pokażę Ci jak pisać kod, aby przetestować kilka możliwości HaMeRa.

Następująca sekcja wprowadzi Cię w znaczenie wątków w tle dla systemu Android. Jeśli jesteś zaznajomiony z tą koncepcją, możesz ją pominąć i przejść bezpośrednio do dyskusji o frameworku HaMeR w sekcji 3.

2. Responsywność dzięki wątkom w tle

Kiedy na Androidzie uruchamiana jest aplikacja, pierwszym wątkiem stworzonym przez ten proces jest wątek główny (ang. main thread), znany również jako wątek interfejsu użytkownika (ang. UI thread), który jest odpowiedzialny za obsługę całej logiki interfejsu użytkownika. Jest to najważniejszy wątek w całej aplikacji. Jest odpowiedzialny za obsługę wszystkich interakcji użytkownika, oraz "wiązanie" ruchomych części aplikacji razem. Android podchodzi do tego bardzo poważnie, i jeśli Twój wątek interfejsu użytkownika zostanie zatrzymany na więcej niż kilka sekund, spowoduje to błąd aplikacji (ang. crash).

[Wątek interfejsu użytkownika] jest bardzo ważny, ponieważ jest odpowiedzialny za wysyłanie zdarzeń (ang. events) do właściwego widżetu UI, włączając w to rysowanie zdarzeń. To również wątek w którym Twoja aplikacja współdziała z komponentami UI Androida (komponenty z pakietów android.widget oraz android.view). Jako główny wątek jest czasem nazywany wątkiem interfejsu użytkownika (ang. UI thread). — Procesy i wątki, Android Developer Guide

Problemem jest to, że domyślnie prawie cały kod w androidowej aplikacji zostanie wykonany w wątku interfejsu użytkownika. Ponieważ zadania wewnątrz wątku są wykonywane sekwencyjnie, interfejs użytkownika zawiesi się (ang. freeze), stając się nieresponsywnym, dopóki będzie przetwarzać inne zadania.

Leave the UI Thread as free as possible using Background Threads

Długotrwałe zadania wywoływane na wątku interfejsu użytkownika są katastrofalne w skutkach dla Twojej aplikacji, dodatkowo będą wyświetlać okno dialogowe ANR (Application Not Responding - aplikacja nie odpowiada).  Nawet krótkie zadania mogą wpłynąć na odczucia użytkownika (ang. user experience), stąd dobrym podejściem jest usunięcie tylu zadań, ile jest tylko możliwe, z wątku interfejsu użytkownika, używając do tego wątków w tle. Jak zostało wcześniej powiedziane - istnieje wiele możliwości do rozwiązania tego problemu. Dlatego będziemy poznawać framework HaMeR - jedno z podstawowych rozwiązań tego problemu w systemie Android.

3. Framework HaMeR

Framework HaMeR umożliwia wątkom w tle wysyłanie komunikatów (ang. messages) lub zadań (ang. runnables) do wątku interfejsu użytkownika, jak i do dowolnego innego wątku MessageQueue poprzez Handlery. HaMeR odnosi się do HandlerMessage, & Runnable. Istnieją inne ważne klasy, które współpracują z HaMeRem: Looper i MessageQueue. Razem, są odpowiedzialne za usprawnienie zarządzania wątkami na Androidzie, dbanie o synchronizację i zapewnianie łatwego dostępu do wątków w tle - na komunikowanie się z interfejsem użytkownika, i innymi wątkami.

Oto jak klasy w HaMeRze są dopasowane do siebie.

The HaMeR framework
  • Looper uruchamia pętlę komunikatów (Message) na wątku używającym MessageQueue.
  • MessageQueue przechowuje listę komunikatów wysłanych do Looper.
  • Handler pozwala na wysyłanie i przetwarzanie komunikatów (Message) i zadań (Runnable) do kolejki (MessageQueue). Może zostać użyty do wysyłania i przetwarzania komunikatów pomiędzy wątkami.
  • Komunikat (Message) zawiera opis i dane które mogą zostać wysłane do Handlera.
  • Runnable reprezentuje zadanie do wykonania.

HaMeR pozwala wątkom wysyłać komunikaty lub obiekty Runnable do siebie, lub do wątku interfejsu użytkownika. HaMeR promuje interakcje wątku tła poprzez Handler.

3.1 Klasa Handler

Handler jest siłą napędową HaMeRa. Jest odpowiedzialny za wysyłanie komunikatów (Message) danych i obiektów zadań (Runnable), do kolejki (MessageQueue) powiązanej z wątkiem (Thread). Po dostarczeniu zadań do kolejki, Handler odbiera obiekty od obiektu Looper i przetwarza komunikaty w odpowiednim czasie, używając instancji Handler powiązanej z nimi.

Handler może być wykorzystywany do wysłania komunikatów (Message) i obiektów zadań (Runnable), pomiędzy wątkami, dopóki wątki dzielą ten sam proces. Inaczej będzie konieczne stworzenie komunikacji między procesowej (Inter Process Communication - IPC), której omówienie wykracza poza zakres tego poradnika.

Instancjonowanie Handlera

Handler musi być powiązany z obiektem Looper, i połączenie to musi zostać utworzone podczas jego instancjonowania. Jeśli Handler nie będzie miał zapewnionego obiektu Looper, to zostanie powiązany z obiektem Looper wątku obecnego.

Pamiętaj, że Handler i Looper zawsze muszą być powiązane, połączenie to jest trwałe i nie może zostać zmienione po ustanowieniu. Niemniej jednak, obiekt Looper wątku może posiadać powiązania z wieloma obiektami Handler. Jest to istotna informacja - Looper musi być aktywny zanim zostanie powiązany z Handlerem.

3.2 Looper i MessageQueue

Współpraca pomiędzy Looper i MessageQueue w wątku Javy tworzy pętlę zadań, które są przetwarzane sekwencyjnie. Pętla będzie podtrzymywać aktywność wątku podczas oczekiwania na kolejne zadania. Wątek może mieć tylko jeden obiekt Looper i jedną MessageQueue powiązaną z nim, może istnieć wiele handlerów dla każdego wątku. Handlery są odpowiedzialne za przetwarzanie zadań w kolejce, każde zadanie wie który handler jest odpowiedzialny za jego przetwarzanie.

3.3. Przygotowywanie wątku dla HaMeRa

UI lub główny wątek są jedynymi rodzajami wątków, które domyślnie posiadają Handler, Looper i MessageQueue. Inne wątki muszę być przygotowane z tamtymi obiektami zanim będą mogły pracować z HaMeRem. Najpierw musimy utworzyć Looper, która już zawiera MessageQueue i dołączyć go do wątku. Możesz zrobić to z podklasą Thread, tak jak poniżej.

Jednak prościej jest skorzystać z pomocniczej klasy zwanej HandlerThread - w której skład wchodzą Looper i MessageQueue, wbudowane w wątek Javy (Thread) - i jest gotowa do odbioru Handlera.

4. Wysyłanie zadań

Runnable jest interfejsem Javy o wielu zastosowaniach. Powinno się to rozumieć jako jedno zadanie, które ma być wykonywane w wątku (Thread). Zawiera jedną metodę, która musi być zaimpementowana Runnable.run(), do wykonania zadania.

Handler umieszcza zadania (Runnable) w kolejce na wiele sposobów.

  • Handler.post(Runnable r): dodaje zadanie (Runnable) do MessageQueue.
  • Handler.postAtFrontOfQueue(Runnable r): dodaje zadanie (Runnable) na początek kolejki MessageQueue.
  • Handler.postAtTime(Runnable r, long timeMillis): dodaje zadanie (Runnable) do kolejki (MessageQueue), które ma być uruchomione w określonym przez timeMillis czasie.
  • Handler.postDelayed(Runnable r, long delay): dodaje zadanie (Runnable) do kolejki aby zostało wykonane po określonej ilości czasu.

Możliwe jest również skorzystanie z domyślnego handlera UI do wysłania zadania (Runnable) wywołując Activity.runOnUiThread().

Należy pamiętać, że w przeciwieństwie do komunikatu (Message), zadanie (Runnable) nie może zostać powtórnie wykorzystane - gdy jego praca zostanie wykonana, kończy swoją aktywność. Ponieważ jest częścią standardowego pakietu Javy, Runnable nie zależy od Handler i może być wywoływane w standardowym wątku (Thread) używając metody Runnable.run() method. Niemniej, to podejście nie ma nic wspólnego z HaMeRem i nie udostępnia żadnych z jego zalet.

5. Wysyłanie komunikatów

Obiekt Message określa komunikat zawierający opis i dowolne dane, tak że może zostać wysłany i przetworzony przez Handler. Komunikat (Message) jest identyfikowany na podstawie zmiennej typu int określonej w Message.what(). Komunikat (Message) może zawierać jeszcze dwa argumenty typu int, oraz jeden typu Object do przechowywania innego typu danych.

  • Message.what: zmienna typu int, Identyfikator komunikatu (Message)
  • Message.arg1: argument typu int, dowolnego przeznaczenia
  • Message.arg2: argument typu int, dowolnego przeznaczenia
  • Message.obj: argument typu Object do przechowania danych dowolnego przeznaczenia.

Gdy chcesz wysłać komunikat - zamiast tworzyć go od początku - zalecanym podejściem jest ponowne wykorzystanie poprzednich komunikatów, z puli komunikatów całej aplikacji poprzez metodę Message.obtain() lub Handler.obtainMessage(). commands. Istnieje kilka wersji metod, które umożliwią Ci uzyskanie komunikatu (Message) dopasowanego do Twoich potrzeb.

Powszechnie stosuje się Handler.obtainMessage(), gdy chcesz wysłać komunikat do wątku w tle.  Użyj instancji Handler powiązanej z instancją Looper tego wątku, aby otrzymać komunikat (Message) i wysłać go do wątku w tle, jak w przykładzie poniżej.

Klasa Message posiada mnóstwo świetnych metod, radzę przyjrzeć się im w dokumentacji.

5.1. Opcje sendMessage()

Podobnie jak przy wysyłaniu zadań (Runnable), istnieje kilka opcji wysyłania komunikatów (Message):

  • Handler.sendMessage( Message msg ): dodaje komunikat (Message) do kolejki (MessageQueue).
  • Handler.sendMessageAtFrontOfQueue( Message msg ): dodaje komunikat (Message) na początek kolejki (MessageQueue).
  • Handler.sendMessageAtTime( Message msg, long timeInMillis ): dodaje komunikat (Message) do kolejki po określonym czasie (czas bezwzględny).
  • Handler.sendMessageDelayed ( Message msg, long timeInMillis ): dodaje komunikat (Message) do kolejki po określonym upływie czasu.

5.2 Obsługa komunikatów

Obiekty Message wysyłane przez obiekt Looper są przetwarzane przez Handler, poprzez metodę Handler.handleMessage. Wszystko co musisz zrobić, to rozszerzenie klasy Handler i nadpisanie (ang. override) tej metody w celu przetworzenia komunikatów.

6. Podsumowanie

Framework HaMeR może pomóc w udoskonaleniu kodu Twoich współbieżnych aplikacji na Androida. Początkowo może się to wydawać nieco zagmatwane - w porównaniu z prostotą AsyncTask - ale otwartość HaMeRa może być zaletą jeśli zostanie prawidłowo użyta.

Pamiętaj:

  • Metody Handler.post()  są używane wtedy, gdy nadawca wie jakie operacje są do wykonania.
  • Metody Handler.sendMessage() są używane wtedy, gdy odbiorca wie jakie operacje są do wykonania.

Aby dowiedzieć się więcej o wątkowaniu na Androidzie, możesz zainteresować się książką "Android. Aplikacje wielowątkowe. Techniki przetwarzania" Andersa Göranssona.

6.1. Co dalej?

W następnym poradniku będziemy kontynuować poznawanie frameworku HaMeR z praktycznym podejściem, poprzez budowanie aplikacji prezentujących odmienne podejścia do korzystania z tego Androidowego współbieżnego frameworku. Stworzymy aplikację z od podstaw, próbując możliwości, takich jak: komunikacja pomiędzy wątkami (Threads), komunikacja z wątkiem UI, jak również wysyłanie komunikatów danych i zadań (Runnable) z opóźnieniem.

Do zobaczenia wkrótce!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.