Sprawdziłem oficjalny Android dokumentacji / instrukcji dla Looper
, Handler
i MessageQueue
. Ale nie mogłem tego zrozumieć. Jestem nowy w Androidzie i bardzo się pomyliłem z tymi koncepcjami.
Odpowiedzi:
A Looper
to pętla obsługi wiadomości: odczytuje i przetwarza elementy z pliku MessageQueue
. Looper
Klasa jest zwykle stosowany w połączeniu z HandlerThread
(podklasą Thread
).
A Handler
to klasa narzędziowa, która ułatwia interakcję z - Looper
głównie poprzez wysyłanie wiadomości i Runnable
obiektów do wątku MessageQueue
. Po utworzeniu Handler
jest on powiązany z określonym Looper
(i skojarzonym wątkiem i kolejką komunikatów).
W typowym użyciu tworzysz i uruchamiasz HandlerThread
, a następnie tworzysz Handler
obiekt (lub obiekty), za pomocą którego inne wątki mogą współdziałać z HandlerThread
instancją. Handler
Muszą być tworzone podczas pracy na HandlerThread
, chociaż raz stworzony nie ma ograniczeń, co można używać nici Handler
„s metod harmonogramowania ( post(Runnable)
itd)
Główny wątek (inaczej wątek interfejsu użytkownika) w aplikacji na Androida jest konfigurowany jako wątek obsługi przed utworzeniem instancji aplikacji.
Oprócz docs klasowych, jest ładny dyskusja o tym wszystkim tutaj .
PS Wszystkie wyżej wymienione zajęcia znajdują się w pakiecie android.os
.
MessageQueue
państwa, że MessageQueue
jest „ klasy niskopoziomowe trzyma listę wiadomości mają być wysyłane przez Looper
. ”
Powszechnie wiadomo, że aktualizowanie elementów interfejsu użytkownika bezpośrednio z wątków innych niż główny wątek w systemie Android jest nielegalne . Ten dokument dotyczący systemu Android ( Obsługa kosztownych operacji w wątku interfejsu użytkownika ) sugeruje kroki, które należy wykonać, jeśli musimy uruchomić oddzielny wątek, aby wykonać kosztowną pracę i zaktualizować interfejs użytkownika po zakończeniu. Chodzi o to, aby utworzyć obiekt Handler powiązany z głównym wątkiem i wysłać do niego Runnable w odpowiednim czasie. To Runnable
będzie powoływać się na głównym wątku . Ten mechanizm jest zaimplementowany w klasach Looper i Handler .
Looper
Klasa utrzymuje kolejka komunikatów , które zawiera listę wiadomości . Ważną cechą Loopera jest to, że jest powiązany z wątkiem, w którym Looper
jest tworzony . To skojarzenie jest utrzymywane na zawsze i nie można go zerwać ani zmienić. Pamiętaj również, że wątek nie może być powiązany z więcej niż jednym Looper
. Aby zagwarantować to powiązanie, Looper
jest przechowywany w pamięci lokalnej wątku i nie można go utworzyć bezpośrednio za pomocą jego konstruktora. Jedynym sposobem, aby go utworzyć jest zwrócenie przygotować metody statyczne Looper
. Przygotowanie metody najpierw bada ThreadLocalbieżącego wątku, aby upewnić się, że nie ma już powiązanego Looper z wątkiem. Po badaniu nowy Looper
jest tworzony i zapisywany w ThreadLocal
. Po przygotowaniu Looper
możemy wywołać na niej metodę loop , aby sprawdzić, czy są nowe wiadomości i Handler
zająć się nimi.
Jak sama nazwa wskazuje, Handler
klasa jest głównie odpowiedzialna za obsługę (dodawanie, usuwanie, wysyłanie) komunikatów aktualnego wątku MessageQueue
. Handler
Przykład jest również związany z gwintem. Wiązania pomiędzy Handler i nici uzyskuje się poprzez Looper
a MessageQueue
. A Handler
jest zawsze powiązany z a Looper
, a następnie powiązany z wątkiem skojarzonym z Looper
. W przeciwieństwie do Looper
wielu instancji programu obsługi można powiązać z tym samym wątkiem. Za każdym razem, gdy wywołujemy post lub inne podobne metody Handler
, do skojarzonego z nim dodawana jest nowa wiadomość MessageQueue
. Pole docelowe wiadomości jest ustawione na bieżącą Handler
instancję. KiedyLooper
odebrał tę wiadomość, wywołuje dispatchMessage w polu docelowym wiadomości, dzięki czemu wiadomość jest kierowana z powrotem do instancji Handler, która ma być obsłużona, ale we właściwym wątku. Relacje między Looper
, Handler
i MessageQueue
przedstawiono poniżej:
Zacznijmy od Loopera. Możesz łatwiej zrozumieć związek między Looper, Handler i MessageQueue, gdy zrozumiesz, czym jest Looper. Możesz także lepiej zrozumieć, czym jest Looper w kontekście frameworka GUI. Looper jest stworzony do robienia 2 rzeczy.
1) Looper przekształca normalny wątek , który kończy się po run()
powrocie metody, w coś, co działa nieprzerwanie do momentu uruchomienia aplikacji na Androida , co jest potrzebne w ramach GUI (technicznie rzecz biorąc, nadal kończy się, gdy run()
metoda zwraca. Ale pozwól mi wyjaśnić, o co mi chodzi, poniżej).
2) Looper zapewnia kolejkę, w której umieszczane są zadania do wykonania, co jest również wymagane w ramach GUI.
Jak być może wiesz, kiedy aplikacja jest uruchamiana, system tworzy dla niej wątek wykonywania, zwany „głównym”, a aplikacje na Androida zwykle działają w całości w pojedynczym wątku, domyślnie „głównym wątku”. Ale główny wątek nie jest jakimś tajnym, specjalnym wątkiem . To po prostu zwykły wątek, który możesz również utworzyć za pomocą new Thread()
kodu, co oznacza, że kończy się, gdy jego run()
metoda zwraca! Pomyśl o poniższym przykładzie.
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
Teraz zastosujmy tę prostą zasadę do aplikacji na Androida. Co by się stało, gdyby aplikacja na Androida była uruchamiana w normalnym wątku? Wątek o nazwie „main” lub „UI” lub jakikolwiek inny uruchamia aplikację i rysuje cały interfejs użytkownika. Tak więc pierwszy ekran jest wyświetlany użytkownikom. Co teraz? Główny wątek się kończy? Nie, nie powinno. Powinien poczekać, aż użytkownicy coś zrobią, prawda? Ale jak możemy osiągnąć takie zachowanie? Cóż, możemy spróbować z Object.wait()
lubThread.sleep()
. Na przykład główny wątek kończy swoje początkowe zadanie, aby wyświetlić pierwszy ekran i usypia. Budzi się, co oznacza, że jest przerywany, gdy pobierana jest nowa praca do wykonania. Jak na razie dobrze, ale w tej chwili potrzebujemy struktury danych podobnej do kolejki do przechowywania wielu zadań. Pomyśl o przypadku, gdy użytkownik kolejno dotyka ekranu, a wykonanie zadania zajmuje więcej czasu. Musimy więc mieć strukturę danych, aby przechowywać zadania do wykonania w trybie „pierwszy na wejściu, pierwszy na wyjściu”. Możesz również sobie wyobrazić, że implementowanie zawsze działającego i przetwarzającego zadania, gdy nadejdzie wątek przy użyciu przerwania, nie jest łatwe i prowadzi do złożonego i często niemożliwego do utrzymania kodu. Wolelibyśmy stworzyć nowy mechanizm do tego celu i na tym właśnie polega Looper . Oficjalny dokument klasy Loopermówi: „Wątki domyślnie nie mają skojarzonej pętli komunikatów”, a Looper to klasa „używana do uruchamiania pętli komunikatów dla wątku”. Teraz możesz zrozumieć, co to oznacza.
Przejdźmy do Handler i MessageQueue. Po pierwsze MessageQueue to kolejka, o której wspomniałem powyżej. Znajduje się wewnątrz Loopera i to wszystko. Możesz to sprawdzić za pomocą kodu źródłowego klasy Looper . Klasa Looper ma zmienną składową MessageQueue.
Więc czym jest Handler? Jeśli jest kolejka, to powinna istnieć metoda, która pozwoli nam umieścić w kolejce nowe zadanie, prawda? To właśnie robi Handler. Możemy umieścić nowe zadanie w kolejce (MessageQueue) używając różnych post(Runnable r)
metod. Otóż to. Chodzi o Looper, Handler i MessageQueue.
Moje ostatnie słowo jest takie, że w zasadzie Looper to klasa stworzona, aby rozwiązać problem występujący w ramach GUI. Ale tego rodzaju potrzeby mogą się również zdarzyć w innych sytuacjach. W rzeczywistości jest to dość znany wzorzec dla aplikacji wielowątkowych i możesz dowiedzieć się o nim więcej w "Programowaniu współbieżnym w Javie" autorstwa Douga Lea (szczególnie pomocny byłby rozdział 4.1.4 "Wątki robocze"). Możesz także sobie wyobrazić, że ten rodzaj mechanizmu nie jest unikalny w ramach systemu Android, ale wszystkie frameworki GUI mogą wymagać czegoś podobnego do tego. Prawie ten sam mechanizm można znaleźć we frameworku Java Swing.
MessageQueue
: Jest to klasa niskiego poziomu przechowująca listę wiadomości do wysłania przez Looper
. Wiadomości nie są dodawane bezpośrednio do a MessageQueue
, ale raczej poprzez Handler
obiekty skojarzone z Looper
. [ 3 ]
Looper
: Przechodzi przez pętlę, MessageQueue
która zawiera wiadomości do wysłania. Faktyczne zadanie zarządzania kolejką wykonuje podmiot Handler
odpowiedzialny za obsługę (dodawanie, usuwanie, wysyłanie) komunikatów w kolejce komunikatów. [ 2 ]
Handler
: To pozwala na wysyłanie i proces Message
i Runnable
przedmioty związane z wątku MessageQueue
. Każda instancja Handler jest powiązana z pojedynczym wątkiem i kolejką komunikatów tego wątku. [ 4 ]
Kiedy tworzysz nowy Handler
, jest on powiązany z wątkiem / kolejką komunikatów wątku, który go tworzy - od tego momentu będzie dostarczał komunikaty i pliki do uruchomienia do tej kolejki komunikatów i wykonywał je, gdy wychodzą z kolejki komunikatów .
Prosimy zapoznać się z poniższym obrazkiem [ 2 ], aby lepiej zrozumieć.
Rozszerzenie odpowiedzi, przez @K_Anas, o przykład, Jak stwierdzono
Powszechnie wiadomo, że aktualizowanie składników interfejsu użytkownika bezpośrednio z wątków innych niż główny wątek w systemie Android jest nielegalne.
na przykład jeśli spróbujesz zaktualizować interfejs użytkownika za pomocą Thread.
int count = 0;
new Thread(new Runnable(){
@Override
public void run() {
try {
while(true) {
sleep(1000);
count++;
textView.setText(String.valueOf(count));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start();
Twoja aplikacja ulegnie awarii z wyjątkiem.
android.view.ViewRoot $ CalledFromWrongThreadException: tylko oryginalny wątek, który utworzył hierarchię widoków, może dotknąć jej widoków.
innymi słowy, musisz użyć, Handler
który zachowuje odniesienie do MainLooper
ie Main Thread
lub UI Thread
i przekazać zadanie jako Runnable
.
Handler handler = new Handler(getApplicationContext().getMainLooper);
int count = 0;
new Thread(new Runnable(){
@Override
public void run() {
try {
while(true) {
sleep(1000);
count++;
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(String.valueOf(count));
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start() ;