Nozioni di base su Android: esecuzione del codice nel thread dell'interfaccia utente


450

Dal punto di vista dell'esecuzione del codice nel thread dell'interfaccia utente, c'è qualche differenza tra:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

o

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

e

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}

Per chiarire la mia domanda: supponevo che quel codice fosse chiamato da un thread di servizio, in genere un ascoltatore. Ho anche supposto che ci fosse un lavoro pesante da compiere nella funzione doInBackground () di AsynkTask o in una nuova attività (...) chiamata prima dei primi due frammenti. In ogni caso, onPostExecute () di AsyncTask viene inserito alla fine della coda degli eventi, giusto?
Luky,

Risposte:


288

Nessuno di questi è esattamente lo stesso, anche se avranno tutti lo stesso effetto netto.

La differenza tra il primo e il secondo è che se ti capita di trovarti sul thread principale dell'applicazione durante l'esecuzione del codice, il primo ( runOnUiThread()) eseguirà Runnableimmediatamente. Il secondo ( post()) inserisce sempre Runnablela fine della coda degli eventi, anche se si è già nel thread dell'applicazione principale.

Il terzo, supponendo che tu crei ed esegua un'istanza di BackgroundTask, perderà molto tempo a prendere un thread dal pool di thread, per eseguire un no-op predefinito doInBackground(), prima di fare ciò che equivale a post(). Questo è di gran lunga il meno efficiente dei tre. Utilizzare AsyncTaskse si ha effettivamente del lavoro da fare in un thread in background, non solo per l'uso di onPostExecute().


27
Si noti inoltre che è AsyncTask.execute()necessario chiamare comunque dal thread dell'interfaccia utente, il che rende questa opzione inutile per il caso d'uso di eseguire semplicemente il codice sul thread dell'interfaccia utente da un thread in background a meno che non si trasferisca tutto il lavoro in background doInBackground()e lo si usi AsyncTaskcorrettamente.
Kabuko,

@kabuko come posso verificare che sto chiamando AsyncTaskdal thread dell'interfaccia utente?
Neil Galiaskarov,

@NeilGaliaskarov Sembra un'opzione solida: stackoverflow.com/a/7897562/1839500
Dick Lucas

1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-geoengineering

1
@NeilGaliaskarov per versioni maggiori o uguali all'uso di MLooper.getMainLooper().isCurrentThread
Balu Sangem,

252

Mi piace quello del commento HPP , può essere usato ovunque senza alcun parametro:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

2
Quanto è efficiente questo? È uguale alle altre opzioni?
EmmanuelMess,

59

C'è un quarto modo di usare Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

56
Dovresti stare attento con questo. Perché se si crea un gestore in un thread non UI, si invieranno messaggi al thread non UI. Un gestore per impostazione predefinita invia un messaggio al thread in cui è stato creato.
Lujop,

108
eseguire sul thread dell'interfaccia utente principale do new Handler(Looper.getMainLooper()).post(r), che è il modo preferito in quanto Looper.getMainLooper()effettua una chiamata statica a main, mentre postOnUiThread()deve avere un'istanza di MainActivitynell'ambito.
HPP,

1
@HPP Non conoscevo questo metodo, sarà un ottimo modo quando non hai né Activity né una View. Funziona alla grande! grazie mille molto!
Sulfkain,

@lujop Lo stesso vale per il metodo di richiamata onPreExecute di AsyncTask.
Sreekanth Karumanaghat,

18

La risposta di Pomber è accettabile, tuttavia non sono un grande fan della creazione ripetuta di nuovi oggetti. Le migliori soluzioni sono sempre quelle che tentano di mitigare la memoria. Sì, c'è la raccolta automatica dei rifiuti ma la conservazione della memoria in un dispositivo mobile rientra nei confini delle migliori pratiche. Il codice seguente aggiorna un TextView in un servizio.

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

Può essere utilizzato da qualsiasi luogo come questo:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);

7
+1 per menzionare la creazione di GC e oggetti. TUTTAVIA, non sono necessariamente d'accordo The best solutions are always the ones that try to mitigate memory hog. Ci sono molti altri criteri per best, e questo ha un leggero odore di premature optimization. Cioè, a meno che tu non sappia che lo chiami abbastanza quel numero di oggetti creati è un problema (rispetto ai diecimila altri modi in cui la tua app sta probabilmente creando immondizia), ciò che potrebbe essere bestè scrivere il più semplice (più facile da capire ) e passare a qualche altra attività.
ToolmakerSteve

2
A proposito, textViewUpdaterHandlersarebbe meglio chiamare qualcosa di simile uiHandlero mainHandler, dal momento che è generalmente utile per qualsiasi post sul thread dell'interfaccia utente principale; non è affatto legato alla tua classe TextViewUpdater. Lo allontanerei dal resto di quel codice e chiarirei che può essere utilizzato altrove ... Il resto del codice è dubbio, perché per evitare di creare dinamicamente un oggetto, si rompe quello che potrebbe essere una singola chiamata in due passaggi setTexte post, che si basano su un oggetto longevo che usi come temporaneo. Complessità non necessaria e non thread-safe. Non facile da mantenere.
ToolmakerSteve

Se davvero avete una situazione in cui questo si chiama così tante volte che vale la pena di caching uiHandlere textViewUpdater, quindi migliorare la vostra classe, cambiando per public void setText(String txt, Handler uiHandler)e aggiungendo metodo integrale uiHandler.post(this); Poi chiamante può fare in un solo passaggio: textViewUpdater.setText("Hello", uiHandler);. Quindi, in futuro, se deve essere thread-safe, il metodo può avvolgere le sue istruzioni all'interno di un blocco uiHandlere il chiamante rimane invariato.
ToolmakerSteve

Sono abbastanza sicuro che puoi eseguire una Runnable solo una volta. Che assolutamente ti rompere l'idea, che è bello in generale.
Nativ,

@Nativ No, Runnable può essere eseguito più volte. Una discussione non può.
Zbyszek,

8

A partire da Android P puoi usare getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

Dai documenti per sviluppatori Android :

Restituisce un Executor che eseguirà attività accodate sul thread principale associato a questo contesto. Questo è il thread utilizzato per inviare chiamate ai componenti dell'applicazione (attività, servizi, ecc.).

Dal CommonsBlog :

È possibile chiamare getMainExecutor () su Context per ottenere un Executor che eseguirà i suoi lavori sul thread principale dell'applicazione. Esistono altri modi per ottenere ciò, usando Looper e un'implementazione personalizzata di Executor, ma questo è più semplice.


7

Se è necessario utilizzare in Frammento è necessario utilizzare

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

invece di

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

Perché ci sarà un'eccezione puntatore null in alcune situazioni come il frammento del cercapersone


1

ciao ragazzi questa è una domanda di base ogni volta che lo dico

utilizzare l' handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

C'è qualche motivo per cui hai scelto di utilizzare il gestore generico per runOnUiThread?
ThePartyTurtle,

Questo darà un java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()se non viene chiamato dal thread dell'interfaccia utente.
Roc Boronat,
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.