Risposte:
A Looper
è un ciclo di gestione dei messaggi: legge ed elabora gli elementi da un file MessageQueue
. La Looper
classe viene solitamente utilizzata insieme a a HandlerThread
(una sottoclasse di Thread
).
A Handler
è una classe di utilità che facilita l'interazione con a, Looper
principalmente inviando messaggi e Runnable
oggetti a quelli del thread MessageQueue
. Quando Handler
viene 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 Handler
o più oggetti mediante i quali altri thread possono interagire con l' HandlerThread
istanza. Il Handler
deve 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
.
MessageQueue
Android MessageQueue
indica che a è una " classe di basso livelloLooper
che contiene l'elenco dei messaggi che devono essere inviati da a . "
È 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 Runnable
verrà richiamato sul thread principale . Questo meccanismo è implementato con le classi Looper e Handler .
La Looper
classe mantiene un MessageQueue , che contiene un elenco di messaggi . Un carattere importante di Looper è che è associato al thread all'interno del quale Looper
viene 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 Looper
viene 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 Handler
affrontarli.
Come indica il nome, la Handler
classe è principalmente responsabile della gestione (aggiunta, rimozione, invio) dei messaggi dei thread correnti MessageQueue
. Un Handler
esempio è inoltre vincolata a un thread. Il legame tra il Gestore e filettatura si ottiene tramite Looper
e 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 Handler
sull'istanza corrente . Quando ilLooper
ricevuto 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
, Handler
ed MessageQueue
è il seguente:
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.
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 Handler
oggetti associati a Looper
. [ 3 ]
Looper
: Scorre su un MessageQueue
che contiene i messaggi da inviare. Il compito effettivo di gestire la coda è svolto dal Handler
responsabile della gestione (aggiunta, rimozione, invio) dei messaggi nella coda dei messaggi. [ 2 ]
Handler
: Consente di inviare ed elaborare Message
e Runnable
oggetti 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.
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 Handler
che mantiene il riferimento a MainLooper
ie Main Thread
o UI Thread
e 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() ;