Qual è lo scopo di Looper e come usarlo?


454

Sono nuovo su Android. Voglio sapere cosa fa la Looperclasse e anche come usarla. Ho letto la documentazione della classe Looper Android ma non riesco a capirla completamente. L'ho visto in molti posti, ma incapace di comprenderne lo scopo. Qualcuno può aiutarmi definendo lo scopo Loopere anche dando un semplice esempio se possibile?


7
Ho appena trovato una spiegazione straordinariamente completa e chiara di Looper e del suo utilizzo su Safari Books Online. Sfortunatamente, sospetto che l'accesso sia gratuito solo per un tempo limitato. safaribooksonline.com/library/view/efficient-android-threading/…
Joe Lapp

1
Gli articoli Android e le pagine di riferimento richiedono la conoscenza e la comprensione di un articolo precedente, prima di poter comprendere quello corrente. Ti suggerisco di leggere gli articoli Attività e servizi nelle guide Api, quindi leggi Handler e Looper. Aiuta anche se hai una comprensione di cosa sia un thread (non un thread Android, ma un thread in generale ... ad esempio POSIX).
FutureSci

1
Ho trovato utile questo articolo: codetheory.in/…
Herman il

Risposte:


396

Cos'è Looper?

Looper è una classe che viene utilizzata per eseguire i Messaggi (Runnables) in una coda. I thread normali non hanno tale coda, ad esempio il thread semplice non ha alcuna coda. Viene eseguito una volta e al termine dell'esecuzione del metodo, il thread non eseguirà un altro messaggio (eseguibile).

Dove possiamo usare la classe Looper?

Se qualcuno vuole eseguire più messaggi (Runnables), allora dovrebbe usare la classe Looper che è responsabile della creazione di una coda nel thread. Ad esempio, durante la scrittura di un'applicazione che scarica file da Internet, possiamo utilizzare la classe Looper per inserire i file da scaricare nella coda.

Come funziona?

C'è un prepare()metodo per preparare il Looper. Quindi puoi usare il loop()metodo per creare un loop di messaggi nel thread corrente e ora il tuo Looper è pronto per eseguire le richieste nella coda fino a quando non esci dal loop.

Ecco il codice con cui è possibile preparare il Looper.

class LooperThread extends Thread {
      public Handler mHandler;

      @Override
      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              @Override
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

17
Un AsyncTask è migliore a tale scopo e meno complesso in quanto incapsula tutta la gestione dei thread.
Fernando Gallego,

4
Dovrebbero avere annotazioni @Override prima dei metodi run () e handleMessage ()
Andrew Mackenzie,

5
La documentazione indica che è necessario chiamare looper.quit. Nel tuo codice sopra, Looper.loop si bloccherà indefinitamente.
AndroidDev

2
Come uscire da un ciclo. Voglio dire dove includere Looper.quit () nell'esempio di codice sopra?
Seenu69,

6
Penso che sarebbe meglio usare HandlerThread che è una classe conveniente per un thread con un looper.
Nimrod Dayan,

287

È possibile comprendere meglio cos'è Looper nel contesto del framework GUI. Looper è fatto per fare 2 cose.

1) Looper trasforma un thread normale , che termina quando ritorna il suo metodo run (), in qualcosa di continuo finché l'app Android non è in esecuzione , che è necessario nel framework GUI (tecnicamente, termina ancora quando restituisce il metodo run (). chiarire cosa intendo di seguito).

2) Looper fornisce una coda in cui vengono accodati i lavori da eseguire, che è anche necessario nel framework della GUI.

Come forse saprai, quando viene lanciata un'applicazione, 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 speciale segreto . È solo un normale thread simile ai thread creati con il new Thread()codice, il che significa che termina quando ritorna il suo metodo run ()! 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 alle app Android. Cosa succederebbe se un'app Android funzionasse su un thread normale? Un thread chiamato "main" o "UI" o qualunque cosa avvii l'applicazione e disegna tutta l'interfaccia utente. Quindi, la prima schermata viene mostrata 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 con Object.wait()oThread.sleep(). Ad esempio, il thread principale termina il 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 di dati simile a una coda per contenere più lavori. Pensa a un caso in cui un utente tocca lo schermo in serie e un'attività impiega più tempo a finire. Quindi, abbiamo bisogno di una struttura di dati per contenere i lavori da svolgere in maniera first-in-first-out. Inoltre, si può immaginare che l'implementazione del thread ever-running-and-process-job-when-arrival tramite interrupt non sia facile e porta a un codice complesso e spesso non mantenibile. Preferiremmo creare un nuovo meccanismo per tale scopo, e questo è lo scopo di 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.

Per rendere le cose più chiare, controlliamo il codice in cui viene trasformato il thread principale. Succede tutto nella classe ActivityThread . Nel suo metodo main (), puoi trovare sotto il codice, che trasforma un normale thread principale in qualcosa di cui abbiamo bisogno.

public final class ActivityThread {
    ...
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        Looper.loop();
        ...
    }
}

e Looper.loop()metodo eseguono il ciclo all'infinito e accodano un messaggio ed elaborano uno alla volta:

public static void loop() {
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
    }
}

Quindi, sostanzialmente Looper è una classe creata per risolvere un problema che si verifica nel framework della GUI. Ma questo tipo di esigenze può verificarsi anche in altre situazioni. In realtà è un modello abbastanza famoso per l'applicazione multi-thread, e puoi saperne di più in " Programmazione concorrente in Java " di Doug Lea (In particolare, il capitolo 4.1.4 "Discussioni dei lavoratori" sarebbe utile). Inoltre, puoi immaginare che questo tipo di meccanismo non sia unico nel framework Android, ma tutto il framework GUI potrebbe aver bisogno di un po 'simile a questo. Puoi trovare quasi lo stesso meccanismo nel framework Java Swing.


26
Questa è l'unica risposta che in realtà spiega qualcosa sul perché la classe Looper sarebbe mai stata usata. Non sono sicuro del perché non sia la risposta migliore, le tre risposte più votate non spiegano nulla.
Andrew Koster,

4
@AK. Ecco perché ho aggiunto questa risposta anche se sembrava troppo tardi. Sono contento che la mia risposta ti abbia aiutato! :)
김준호

1
@ Hey-men-whatsup Sì
김준호

1
Prima di leggere questo ero come "Looper ???" e ora "Oh sì, parliamone". Grazie amico, ottima risposta :)
umerk44,

Domanda veloce. Hai dichiarato che nel thread principale dopo aver estratto tutti gli elementi dell'interfaccia utente viene messo in stop. Ma supponiamo che l'utente interagisca con un pulsante sullo schermo, che il clic del pulsante non sia nemmeno inserito in una coda principale, quindi un oggetto lo invierà all'attività corretta, quindi il thread principale per quell'attività è attivo e verrà eseguito il codice per nel callback per quel pulsante fare clic?
CapturedTree

75

Il looper consente di eseguire le attività in sequenza su un singolo thread. E il gestore definisce quei compiti che dobbiamo eseguire. È uno scenario tipico che sto cercando di illustrare in questo esempio:

class SampleLooper extends Thread {
@Override
public void run() {
  try {
    // preparing a looper on current thread     
    // the current thread is being detected implicitly
    Looper.prepare();

    // now, the handler will automatically bind to the
    // Looper that is attached to the current thread
    // You don't need to specify the Looper explicitly
    handler = new Handler();

    // After the following line the thread will start
    // running the message loop and will not normally
    // exit the loop unless a problem happens or you
    // quit() the looper (see below)
    Looper.loop();
  } catch (Throwable t) {
    Log.e(TAG, "halted due to an error", t);
  } 
}
}

Ora possiamo usare il gestore in alcuni altri thread (diciamo ui thread) per pubblicare l'attività su Looper da eseguire.

handler.post(new Runnable()
{
public void run() {
//This will be executed on thread using Looper.
    }
});

Sul thread dell'interfaccia utente abbiamo un Looper implicito che ci consente di gestire i messaggi sul thread dell'interfaccia utente.


non bloccherà alcun processo dell'interfaccia utente, è vero?
Gumuruh,

4
Grazie per aver incluso un campione su come pubblicare "lavori" in coda
Peter Lillevold,

Questo non spiega perché uno dovrebbe usare questa classe, solo come.
Andrew Koster,

33

Android Looperè un wrapper di allegare MessageQueuea Threade gestisce l'elaborazione della coda. Sembra molto criptico nella documentazione di Android e molte volte potremmo incontrare Looperproblemi di accesso all'interfaccia utente. Se non capiamo le basi, diventa molto difficile da gestire.

Ecco un articolo che spiega Looperil ciclo di vita, come usarlo e l'utilizzo di LooperaHandler

inserisci qui la descrizione dell'immagine

Looper = Thread + MessageQueue


3
Questo non spiega perché uno dovrebbe usare questa classe, solo come.
Andrew Koster,

14

Definizione di Looper & Handler:

Looper è una classe che trasforma un thread in un thread della pipeline e Handler ti dà un meccanismo per spingere i task in esso da qualsiasi altro thread.

Dettagli:

Quindi un thread PipeLine è un thread che può accettare più attività da altri thread tramite un gestore.

Il Looper è chiamato così perché implementa il ciclo - prende il compito successivo, lo esegue, quindi prende il successivo e così via. Il gestore viene chiamato gestore perché viene utilizzato per gestire o accettare l'attività successiva ogni volta da qualsiasi altro thread e passare al Looper (Thread o ThreadLine Thread).

Esempio:

Un esempio molto perfetto di Looper and Handler o PipeLine è scaricare più di una immagine o caricarle su un server (Http) una alla volta in un singolo thread invece di avviare un nuovo thread per ogni chiamata di rete in background.

Leggi di più qui su Looper and Handler e la definizione di Pipeline Thread:

Android Guts: introduzione a loopers e gestori


7

Un Looper ha un synchronized MessageQueueutilizzato per elaborare i messaggi inseriti nella coda.

Implementa un Threadmodello di archiviazione specifico.

Solo uno Looperper Thread. Metodi chiave includono prepare(), loop()e quit().

prepare()inizializza la corrente Threadcome a Looper. prepare()è un staticmetodo che utilizza la ThreadLocalclasse come mostrato di seguito.

   public static void prepare(){
       ...
       sThreadLocal.set
       (new Looper());
   }
  1. prepare() deve essere chiamato esplicitamente prima di eseguire il ciclo degli eventi.
  2. loop()esegue il loop degli eventi che attende che arrivino i Messaggi su uno specifico messaggio di Thread. Una volta ricevuto il messaggio successivo, il loop()metodo invia il messaggio al gestore di destinazione
  3. quit()arresta il loop degli eventi. Non termina il ciclo, ma invece accoda un messaggio speciale

Looperpuò essere programmato in a Threadtramite diversi passaggi

  1. Estendere Thread

  2. Chiama Looper.prepare()per inizializzare il thread come aLooper

  3. Crea uno o più Handler(s) per elaborare i messaggi in arrivo

  4. Chiama Looper.loop()per elaborare i messaggi fino a quando non viene richiesto il loop quit().

5

Durata della vita di java La discussione è terminata dopo il completamento del run()metodo. Lo stesso thread non può essere riavviato.

Il looper trasforma il normale Threadin un loop di messaggi. I metodi chiave di Loopersono:

void prepare ()

Inizializza il thread corrente come looper. Questo ti dà la possibilità di creare gestori che fanno poi riferimento a questo looper, prima di avviare effettivamente il loop. Assicurati di chiamare loop () dopo aver chiamato questo metodo e terminalo chiamando quit ().

void loop ()

Esegui la coda messaggi in questo thread. Assicurati di chiamare quit () per terminare il loop.

void quit()

Esce dal crochet.

Fa terminare il metodo loop () senza elaborare altri messaggi nella coda dei messaggi.

Questo articolo di Mindorks di Janishar spiega i concetti chiave in modo piacevole.

inserisci qui la descrizione dell'immagine

Looperè associato a un thread. Se è necessario Loopersul thread dell'interfaccia utente, Looper.getMainLooper()verrà restituito il thread associato.

Devi Looperessere associato a un gestore .

Looper, Handlere HandlerThreadsono il modo Android di risolvere i problemi della programmazione asincrona.

Una volta che hai Handler, puoi chiamare sotto le API.

post (Runnable r)

Fa sì che Runnable r venga aggiunto alla coda dei messaggi. Il runnable verrà eseguito sul thread a cui è collegato questo gestore.

boolean sendMessage (Message msg)

Invia un messaggio alla fine della coda dei messaggi dopo tutti i messaggi in sospeso prima dell'ora corrente. Verrà ricevuto in handleMessage (Message), nel thread allegato a questo gestore.

HandlerThread è una classe utile per l'avvio di un nuovo thread con un looper. Il looper può quindi essere utilizzato per creare classi di gestori

In alcuni scenari, non è possibile eseguire Runnableattività sul thread dell'interfaccia utente. ad es. Operazioni di rete: inviare un messaggio su un socket, aprire un URL e ottenere contenuti leggendoInputStream

In questi casi, HandlerThreadè utile. È possibile ottenere Looperoggetti da HandlerThreade creare una Handlersul HandlerThreadposto di thread principale.

Il codice HandlerThread sarà così:

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

Fare riferimento al post seguente per esempio codice:

Android: brindisi in un thread


5

Comprensione dei thread Looper

Un thread java un'unità di esecuzione progettata per eseguire un'attività nel suo metodo run () e terminare in seguito: inserisci qui la descrizione dell'immagine

Ma in Android ci sono molti casi d'uso in cui è necessario mantenere vivo un thread e attendere input / eventi dell'utente, ad es. Discussione UI aka Main Thread.

Il thread principale in Android è un thread Java che viene avviato da JVM all'avvio di un'app e continua a funzionare fino a quando l'utente sceglie di chiuderlo o incontra eccezioni non gestite.

All'avvio di un'applicazione, il sistema crea un thread di esecuzione per l'applicazione, chiamato "principale". Questo thread è molto importante perché è responsabile dell'invio di eventi ai widget dell'interfaccia utente appropriati, inclusi gli eventi di disegno.

inserisci qui la descrizione dell'immagine

Ora il punto da notare qui è sebbene il thread principale sia il thread Java, ma continua ad ascoltare gli eventi dell'utente e disegna frame a 60 fps sullo schermo e comunque non morirà dopo ogni ciclo. com'è così?

La risposta è la classe Looper : Looper è una classe utilizzata per mantenere attivo un thread e gestire una coda di messaggi per eseguire attività su quel thread.

I thread per impostazione predefinita non hanno un loop di messaggi associato ma è possibile assegnarne uno chiamando Looper.prepare () nel metodo run e quindi chiamando Looper.loop ().

Lo scopo di Looper è di mantenere in vita un thread e attendere che il ciclo successivo Messagedell'oggetto di input esegua il calcolo che altrimenti verrà distrutto dopo il primo ciclo di esecuzione.

Se vuoi approfondire il modo in cui Looper gestisce la Messagecoda degli oggetti, puoi dare un'occhiata al codice sorgente di Looperclass:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/os/Looper.java

Di seguito è riportato un esempio di come è possibile creare un Looper Threade comunicare con la Activityclasse utilizzandoLocalBroadcast

class LooperThread : Thread() {

    // sendMessage success result on UI
    private fun sendServerResult(result: String) {
        val resultIntent = Intent(ServerService.ACTION)
        resultIntent.putExtra(ServerService.RESULT_CODE, Activity.RESULT_OK)
        resultIntent.putExtra(ServerService.RESULT_VALUE, result)
        LocalBroadcastManager.getInstance(AppController.getAppController()).sendBroadcast(resultIntent)
    }

    override fun run() {
        val looperIsNotPreparedInCurrentThread = Looper.myLooper() == null

        // Prepare Looper if not already prepared
        if (looperIsNotPreparedInCurrentThread) {
            Looper.prepare()
        }

        // Create a handler to handle messaged from Activity
        handler = Handler(Handler.Callback { message ->
            // Messages sent to Looper thread will be visible here
            Log.e(TAG, "Received Message" + message.data.toString())

            //message from Activity
            val result = message.data.getString(MainActivity.BUNDLE_KEY)

            // Send Result Back to activity
            sendServerResult(result)
            true
        })

        // Keep on looping till new messages arrive
        if (looperIsNotPreparedInCurrentThread) {
            Looper.loop()
        }
    }

    //Create and send a new  message to looper
    fun sendMessage(messageToSend: String) {
        //Create and post a new message to handler
        handler!!.sendMessage(createMessage(messageToSend))
    }


    // Bundle Data in message object
    private fun createMessage(messageToSend: String): Message {
        val message = Message()
        val bundle = Bundle()
        bundle.putString(MainActivity.BUNDLE_KEY, messageToSend)
        message.data = bundle
        return message
    }

    companion object {
        var handler: Handler? = null // in Android Handler should be static or leaks might occur
        private val TAG = javaClass.simpleName

    }
}

Utilizzo :

 class MainActivity : AppCompatActivity() {

    private var looperThread: LooperThread? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // start looper thread
        startLooperThread()

        // Send messages to Looper Thread
        sendMessage.setOnClickListener {

            // send random messages to looper thread
            val messageToSend = "" + Math.random()

            // post message
            looperThread!!.sendMessage(messageToSend)

        }   
    }

    override fun onResume() {
        super.onResume()

        //Register to Server Service callback
        val filterServer = IntentFilter(ServerService.ACTION)
        LocalBroadcastManager.getInstance(this).registerReceiver(serverReceiver, filterServer)

    }

    override fun onPause() {
        super.onPause()

        //Stop Server service callbacks
     LocalBroadcastManager.getInstance(this).unregisterReceiver(serverReceiver)
    }


    // Define the callback for what to do when data is received
    private val serverReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val resultCode = intent.getIntExtra(ServerService.RESULT_CODE, Activity.RESULT_CANCELED)
            if (resultCode == Activity.RESULT_OK) {
                val resultValue = intent.getStringExtra(ServerService.RESULT_VALUE)
                Log.e(MainActivity.TAG, "Server result : $resultValue")

                serverOutput.text =
                        (serverOutput.text.toString()
                                + "\n"
                                + "Received : " + resultValue)

                serverScrollView.post( { serverScrollView.fullScroll(View.FOCUS_DOWN) })
            }
        }
    }

    private fun startLooperThread() {

        // create and start a new LooperThread
        looperThread = LooperThread()
        looperThread!!.name = "Main Looper Thread"
        looperThread!!.start()

    }

    companion object {
        val BUNDLE_KEY = "handlerMsgBundle"
        private val TAG = javaClass.simpleName
    }
}

Possiamo invece utilizzare l'attività Async o i servizi di intento?

  • Le attività asincrone sono progettate per eseguire una breve operazione in background e fornire progressi e risultati sul thread dell'interfaccia utente. Le attività asincrone hanno limiti in quanto non è possibile creare più di 128 attività asincrone e ThreadPoolExecutorconsentiranno solo fino a 5 attività asincrone .

  • IntentServicessono inoltre progettati per eseguire attività in background per un po 'più di tempo e puoi usare LocalBroadcastper comunicare Activity. Ma i servizi vengono distrutti dopo l'esecuzione dell'attività. Se vuoi mantenerlo in esecuzione per molto tempo di quanto sia necessario fare diamine come while(true){...}.

Altri casi d'uso significativi per looper thread:

  • Utilizzato per la comunicazione socket a 2 vie in cui il server continua ad ascoltare il socket client e riscrivere il riconoscimento

  • Elaborazione bitmap in background. Passa l'URL dell'immagine al thread Looper e applicherà gli effetti filtro e lo memorizzerà nella posizione temporanea e quindi trasmetterà il percorso temporaneo dell'immagine.


4

Questa risposta non ha nulla a che fare con la domanda, ma l'uso del looper e il modo in cui le persone hanno creato il gestore e il looper in TUTTE le risposte qui sono semplicemente cattive pratiche (alcune spiegazioni sono corrette però), devo pubblicare questo:

HandlerThread thread = new HandlerThread(threadName);
thread.start();
Looper looper = thread.getLooper();
Handler myHandler = new Handler(looper);

e per una piena attuazione


3

La gestione di più elementi down o upload in un servizio è un esempio migliore.

Handlere AsnycTaskvengono spesso utilizzati per propagare eventi / messaggi tra l' interfaccia utente (thread) e un thread di lavoro o per ritardare le azioni. Quindi sono più correlati all'interfaccia utente.

A Loopergestisce le attività ( Runnables, Futures ) in una coda correlata al thread in background, anche senza interazione dell'utente o un'interfaccia utente visualizzata (l'app scarica un file in background durante una chiamata).


1

Cos'è Looper?

DAI DOCUMENTI

Looper

LooperClasse utilizzata per eseguire un ciclo di messaggi per a thread. Ai thread di default non è associato un loop di messaggi; per crearne uno, chiama prepare()il thread che deve eseguire il loop, quindi loop()fai in modo che elabori i messaggi fino a quando il loop non viene arrestato.

  • A Looperè un ciclo di gestione dei messaggi:
  • Un personaggio importante di Looper è che è associato al thread all'interno del quale viene creato il Looper
  • La classe Looper mantiene un MessageQueue, che contiene un elenco di messaggi. Un personaggio importante di Looper è che è associato al thread all'interno del quale viene creato il Looper.
  • Il Loopernome è così perché implementa il ciclo: prende il compito successivo, lo esegue, quindi prende il successivo e così via. Si Handlerchiama gestore perché qualcuno non è riuscito a inventare un nome migliore
  • Android Looperè una classe Java nell'interfaccia utente di Android che insieme alla classe Handler per elaborare eventi dell'interfaccia utente come clic sui pulsanti, ridisegni dello schermo e interruttori di orientamento.

Come funziona?

inserisci qui la descrizione dell'immagine

Creazione di Looper

Un thread ottiene a Loopere MessageQueuechiamando Looper.prepare()dopo la sua esecuzione. Looper.prepare()identifica il thread chiamante, crea un Looper e un MessageQueueoggetto e associa il thread

CODICE DI ESEMPIO

class MyLooperThread extends Thread {

      public Handler mHandler; 

      public void run() { 

          // preparing a looper on current thread  
          Looper.prepare();

          mHandler = new Handler() { 
              public void handleMessage(Message msg) { 
                 // process incoming messages here
                 // this will run in non-ui/background thread
              } 
          }; 

          Looper.loop();
      } 
  }

Per ulteriori informazioni, consultare il post seguente


eccellente, ho capito la meccanica. grazie
Serg Burlaka
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.