Qual è la relazione tra Looper, Handler e MessageQueue in Android?


95

Ho controllato la documentazione / guida ufficiale per Android Looper, Handlere MessageQueue. Ma non sono riuscito a capirlo. Sono nuovo su Android e sono rimasto molto confuso con questi concetti.

Risposte:


103

A Looperè un ciclo di gestione dei messaggi: legge ed elabora gli elementi da un file MessageQueue. La Looperclasse viene solitamente utilizzata insieme a a HandlerThread(una sottoclasse di Thread).

A Handlerè una classe di utilità che facilita l'interazione con a, Looperprincipalmente inviando messaggi e Runnableoggetti a quelli del thread MessageQueue. Quando Handlerviene creato, viene associato a uno specifico Looper(e thread associato e coda di messaggi).

Nell'utilizzo tipico, si crea e si avvia un HandlerThread, quindi si crea uno Handlero più oggetti mediante i quali altri thread possono interagire con l' HandlerThreadistanza. Il Handlerdeve essere creato durante l'esecuzione sul HandlerThread, anche se una volta creato non ci sono restrizioni su quali filettature possono utilizzare i Handler's metodi di schedulazione ( post(Runnable)etc.)

Il thread principale (noto anche come thread dell'interfaccia utente) in un'applicazione Android viene configurato come thread del gestore prima della creazione dell'istanza dell'applicazione.

A parte i documenti di classe, c'è una bella discussione di tutto questo qui .

PS Tutte le classi sopra menzionate sono nel pacchetto android.os.


@Ted Hopp - La coda dei messaggi di Looper è diversa dalla coda dei messaggi di Thread?
CopsOnRoad

2
@ Jack - Sono la stessa cosa. La documentazione dell'APIMessageQueue Android MessageQueueindica che a è una " classe di basso livelloLooper che contiene l'elenco dei messaggi che devono essere inviati da a . "
Ted Hopp

95

È noto che è illegale aggiornare i componenti dell'interfaccia utente direttamente da thread diversi dal thread principale in Android. Questo documento Android ( Gestione di operazioni costose nel thread dell'interfaccia utente ) suggerisce i passaggi da seguire se è necessario avviare un thread separato per eseguire un lavoro costoso e aggiornare l'interfaccia utente al termine. L'idea è di creare un oggetto Handler associato al thread principale e postarvi un Runnable al momento opportuno. Questo Runnableverrà richiamato sul thread principale . Questo meccanismo è implementato con le classi Looper e Handler .

La Looperclasse mantiene un MessageQueue , che contiene un elenco di messaggi . Un carattere importante di Looper è che è associato al thread all'interno del quale Looperviene creato . Questa associazione è mantenuta per sempre e non può essere interrotta né modificata. Tieni inoltre presente che un thread non può essere associato a più di uno Looper. Per garantire questa associazione, Looperè archiviato nella memoria locale del thread e non può essere creato direttamente tramite il suo costruttore. L'unico modo per crearlo è chiamare il metodo prepare static suLooper . Il metodo di preparazione esamina prima ThreadLocaldel thread corrente per assicurarsi che non ci sia già un Looper associato al thread. Dopo l'esame, ne Looperviene creato uno nuovo e salvato in ThreadLocal. Dopo aver preparato il Looper, possiamo chiamare il metodo loop su di esso per verificare la presenza di nuovi messaggi e Handleraffrontarli.

Come indica il nome, la Handlerclasse è principalmente responsabile della gestione (aggiunta, rimozione, invio) dei messaggi dei thread correnti MessageQueue. Un Handleresempio è inoltre vincolata a un thread. Il legame tra il Gestore e filettatura si ottiene tramite Loopere MessageQueue. A Handlerè sempre associato a a Looper, e successivamente al thread associato a Looper. Diversamente Looper, più istanze di Handler possono essere associate allo stesso thread. Ogni volta che chiamiamo post o metodi simili su Handler, viene aggiunto un nuovo messaggio al file MessageQueue. Il campo di destinazione del messaggio è impostato Handlersull'istanza corrente . Quando ilLooperricevuto questo messaggio, richiama dispatchMessage sul campo di destinazione del messaggio, in modo che il messaggio torni all'istanza di Handler per essere gestito, ma sul thread corretto. I rapporti tra Looper, Handlered MessageQueueè il seguente:

inserisci qui la descrizione dell'immagine


5
Grazie! ma che cosa è il punto se il gestore prima inviare il messaggio alla coda di messaggi e quindi gestire il messaggio dalla stessa coda? perché non gestisce direttamente il messaggio?
Blake

4
@ Blake b / c stai postando da un thread (thread non looper) ma
gestisci

Molto meglio di quanto documentato su developer.android.com , ma sarebbe bello vedere il codice per il diagramma che hai fornito.
tfmontague

@numansalati - Non è possibile che Handler invii messaggi dal thread del looper?
CopsOnRoad

78

Cominciamo con il Looper. Puoi capire più facilmente la relazione tra Looper, Handler e MessageQueue quando capisci cos'è Looper. Inoltre puoi capire meglio cos'è Looper nel contesto del framework GUI. Looper è fatto per fare 2 cose.

1) Looper trasforma un normale thread , che termina quando il suo run()metodo ritorna, in qualcosa che viene eseguito continuamente fino a quando l'app Android non è in esecuzione , che è necessario nel framework GUI (tecnicamente, termina ancora quandorun() metodo ritorna. Ma lasciatemi chiarire cosa intendo, sotto).

2) Looper fornisce una coda cui vengono accodati i lavori da eseguire, necessaria anche nel framework GUI.

Come forse saprai, quando un'applicazione viene lanciata, il sistema crea un thread di esecuzione per l'applicazione, chiamato “main”, e le applicazioni Android normalmente vengono eseguite interamente su un singolo thread per impostazione predefinita il “thread principale”. Ma il thread principale non è un thread segreto e speciale . È solo un normale thread che puoi anche creare con il new Thread()codice, il che significa che termina quando il suo run()metodo ritorna! Pensa all'esempio seguente.

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();
    }
}

Ora applichiamo questo semplice principio all'app Android. Cosa succederebbe se un'app Android fosse eseguita su un thread normale? Un thread chiamato "main" o "UI" o qualsiasi altra cosa avvia l'applicazione e disegna tutta l'interfaccia utente. Quindi, la prima schermata viene visualizzata agli utenti. Così quello che ora? Il thread principale termina? No, non dovrebbe. Dovrebbe aspettare che gli utenti facciano qualcosa, giusto? Ma come possiamo ottenere questo comportamento? Bene, possiamo provare conObject.wait() oThread.sleep(). Ad esempio, il thread principale termina il suo lavoro iniziale per visualizzare la prima schermata e dorme. Si sveglia, il che significa interrotto, quando viene recuperato un nuovo lavoro da fare. Fin qui tutto bene, ma in questo momento abbiamo bisogno di una struttura dati simile a una coda per contenere più lavori. Pensa a un caso in cui un utente tocca lo schermo in serie e il completamento di un'attività richiede più tempo. Quindi, abbiamo bisogno di una struttura dati per contenere i lavori da svolgere in modo first-in-first-out. Inoltre, puoi immaginare, l'implementazione di thread sempre in esecuzione e processo-quando-arriva utilizzando interrupt non è facile e porta a codice complesso e spesso non mantenibile. Preferiamo creare un nuovo meccanismo per tale scopo, e questo è ciò di cui parla Looper . Il documento ufficiale della classe Looperdice, "I thread per impostazione predefinita non hanno un loop di messaggi associato", e Looper è una classe "utilizzata per eseguire un loop di messaggi per un thread". Ora puoi capire cosa significa.

Passiamo a Handler e MessageQueue. Innanzitutto, MessageQueue è la coda che ho menzionato sopra. Risiede all'interno di un Looper, e basta. Puoi verificarlo con il codice sorgente della classe Looper . La classe Looper ha una variabile membro di MessageQueue.

Allora, cos'è Handler? Se c'è una coda, allora dovrebbe esserci un metodo che dovrebbe permetterci di accodare una nuova attività alla coda, giusto? Questo è ciò che fa Handler. Possiamo accodare una nuova attività in una coda (MessageQueue) usando varipost(Runnable r) metodi. Questo è tutto. Questo è tutto su Looper, Handler e MessageQueue.

La mia ultima parola è, quindi fondamentalmente Looper è una classe creata per risolvere un problema che si verifica nel framework GUI. Ma questo tipo di esigenze può verificarsi anche in altre situazioni. In realtà è un modello piuttosto famoso per applicazioni multi thread, e puoi saperne di più in "Programmazione concorrente in Java" di Doug Lea (in particolare, il capitolo 4.1.4 "Thread di lavoro" sarebbe utile). Inoltre, puoi immaginare che questo tipo di meccanismo non sia unico nel framework Android, ma tutti i framework GUI potrebbero richiedere qualcosa di simile a questo. Puoi trovare quasi lo stesso meccanismo nel framework Java Swing.


4
Migliore risposta. Appreso di più da questa spiegazione dettagliata. Mi chiedo se ci sia qualche post sul blog che vada più in dettaglio.
capt

I messaggi possono essere aggiunti a MessageQueue senza utilizzare Handler?
CopsOnRoad

@CopsOnRoad no non possono essere aggiunti direttamente.
Faisal Naseer

Ha reso la mia giornata ... tanto amore per te :)
Rahul Matte il

26

MessageQueue: È una classe di basso livello che contiene l'elenco dei messaggi che devono essere inviati da un file Looper. I messaggi non vengono aggiunti direttamente a MessageQueue, ma piuttosto tramite Handleroggetti associati a Looper. [ 3 ]

Looper: Scorre su un MessageQueueche contiene i messaggi da inviare. Il compito effettivo di gestire la coda è svolto dal Handlerresponsabile della gestione (aggiunta, rimozione, invio) dei messaggi nella coda dei messaggi. [ 2 ]

Handler: Consente di inviare ed elaborare Messagee Runnableoggetti associati a un thread MessageQueue. Ogni istanza di Handler è associata a un singolo thread e alla coda dei messaggi di quel thread. [ 4 ]

Quando si crea un nuovo Handler, viene associato al thread / coda di messaggi del thread che lo sta creando: da quel punto in poi, consegnerà i messaggi e gli eseguibili a quella coda di messaggi e li eseguirà non appena escono dalla coda dei messaggi .

Si prega gentilmente di esaminare l'immagine sottostante [ 2 ] per una migliore comprensione.

inserisci qui la descrizione dell'immagine


0

Estendendo la risposta, di @K_Anas, con un esempio, come affermato

È noto che è illegale aggiornare i componenti dell'interfaccia utente direttamente da thread diversi dal thread principale in Android.

ad esempio, se provi ad aggiornare l'interfaccia utente utilizzando 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();

la tua app andrà in crash con eccezioni.

android.view.ViewRoot $ CalledFromWrongThreadException: solo il thread originale che ha creato una gerarchia di visualizzazione può toccare le sue visualizzazioni.

in altre parole è necessario utilizzare Handlerche mantiene il riferimento a MainLooper ie Main Threado UI Threade passa l'attività come 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() ;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.