Jaki jest związek między Looper, Handler i MessageQueue w systemie Android?

95

Sprawdziłem oficjalny Android dokumentacji / instrukcji dla Looper, Handleri MessageQueue. Ale nie mogłem tego zrozumieć. Jestem nowy w Androidzie i bardzo się pomyliłem z tymi koncepcjami.

Blake
źródło

Odpowiedzi:

103

A Looperto pętla obsługi wiadomości: odczytuje i przetwarza elementy z pliku MessageQueue. LooperKlasa jest zwykle stosowany w połączeniu z HandlerThread(podklasą Thread).

A Handlerto klasa narzędziowa, która ułatwia interakcję z - Loopergłównie poprzez wysyłanie wiadomości i Runnableobiektów do wątku MessageQueue. Po utworzeniu Handlerjest 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 Handlerobiekt (lub obiekty), za pomocą którego inne wątki mogą współdziałać z HandlerThreadinstancją. HandlerMuszą 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.

Ted Hopp
źródło
@Ted Hopp - Czy kolejka komunikatów Loopera różni się od kolejki wiadomości wątku?
CopsOnRoad
2
@Jack - Są tym samym. Android docs API dlaMessageQueue państwa, że MessageQueuejest „ klasy niskopoziomowe trzyma listę wiadomości mają być wysyłane przez Looper.
Ted Hopp
95

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 Runnablebędzie powoływać się na głównym wątku . Ten mechanizm jest zaimplementowany w klasach Looper i Handler .

LooperKlasa 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 Looperjest 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, Looperjest 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 Looperjest tworzony i zapisywany w ThreadLocal. Po przygotowaniu Loopermożemy wywołać na niej metodę loop , aby sprawdzić, czy są nowe wiadomości i Handlerzająć się nimi.

Jak sama nazwa wskazuje, Handlerklasa jest głównie odpowiedzialna za obsługę (dodawanie, usuwanie, wysyłanie) komunikatów aktualnego wątku MessageQueue. HandlerPrzykład jest również związany z gwintem. Wiązania pomiędzy Handler i nici uzyskuje się poprzez Loopera MessageQueue. A Handlerjest zawsze powiązany z a Looper, a następnie powiązany z wątkiem skojarzonym z Looper. W przeciwieństwie do Looperwielu 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ą Handlerinstancję. KiedyLooperodebrał 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, Handleri MessageQueueprzedstawiono poniżej:

wprowadź opis obrazu tutaj

K_Anas
źródło
5
Dzięki! ale jaki jest sens, jeśli program obsługi najpierw wyśle wiadomość do kolejki komunikatów, a następnie obsłuży wiadomość z tej samej kolejki? dlaczego po prostu nie obsługuje bezpośrednio wiadomości?
Blake,
4
@Blake b / Piszesz z jednej nici wątku (nie looper), ale obsługi wiadomości w innym chwytacz nitki gwintu ()
Numan salati
Znacznie lepsze niż to, co jest udokumentowane na developer.android.com - ale miło byłoby zobaczyć kod dla dostarczonego diagramu.
tfmontague
@numansalati - Czy Handler nie może wysyłać wiadomości z wątku looper?
CopsOnRoad
78

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.

김준호
źródło
4
Najlepsza odpowiedź. Dowiedziałem się więcej z tego szczegółowego wyjaśnienia. Zastanawiam się, czy jest jakiś wpis na blogu, który jest bardziej szczegółowy.
capt.swag
Czy wiadomości można dodawać do MessageQueue bez użycia programu Handler?
CopsOnRoad
@CopsOnRoad nie, nie można ich dodać bezpośrednio.
Faisal Naseer
Zrobiłem mój dzień ... dużo miłości dla Ciebie :)
Rahul Matte
26

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 Handlerobiekty skojarzone z Looper. [ 3 ]

Looper: Przechodzi przez pętlę, MessageQueuektóra zawiera wiadomości do wysłania. Faktyczne zadanie zarządzania kolejką wykonuje podmiot Handlerodpowiedzialny za obsługę (dodawanie, usuwanie, wysyłanie) komunikatów w kolejce komunikatów. [ 2 ]

Handler: To pozwala na wysyłanie i proces Messagei Runnableprzedmioty 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ć.

wprowadź opis obrazu tutaj

AnV
źródło
0

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ć, Handlerktóry zachowuje odniesienie do MainLooper ie Main Threadlub UI Threadi 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() ;
Faisal Naseer
źródło