Android-Grundlagen: Ausführen von Code im UI-Thread

450

Gibt es im Hinblick auf die Ausführung von Code im UI-Thread einen Unterschied zwischen:

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

oder

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

und

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
quelle
Um meine Frage zu klären: Ich nahm an, dass dieser Code von einem Service-Thread aufgerufen wurde, normalerweise von einem Listener. Ich nahm auch an, dass entweder in der Funktion doInBackground () der AsynkTask oder in einer neuen Aufgabe (...), die vor den ersten beiden Snippets aufgerufen wird, eine schwere Arbeit zu erledigen ist. Wie auch immer, onPostExecute () der AsyncTask wird am Ende der Ereigniswarteschlange platziert, oder?
Luky

Antworten:

288

Keines davon ist genau gleich, obwohl alle den gleichen Nettoeffekt haben.

Der Unterschied zwischen der ersten und der zweite ist , dass , wenn Sie sich gerade auf dem Hauptanwendungsthread , wenn der Code ausgeführt wird , der erste ( runOnUiThread()) die ausgeführt wird Runnablesofort. Mit der zweiten ( post()) steht die RunnableWarteschlange immer am Ende der Ereigniswarteschlange, auch wenn Sie sich bereits im Hauptanwendungsthread befinden.

Die dritte, vorausgesetzt, Sie erstellen und führen eine Instanz von aus BackgroundTask, verschwendet viel Zeit damit, einen Thread aus dem Thread-Pool zu holen, um ein Standard-No-Op auszuführen doInBackground(), bevor Sie schließlich das tun, was a entspricht post(). Dies ist bei weitem die am wenigsten effiziente der drei. Verwenden AsyncTaskSie diese Option, wenn Sie tatsächlich in einem Hintergrund-Thread arbeiten müssen, nicht nur für die Verwendung von onPostExecute().

CommonsWare
quelle
27
Beachten AsyncTask.execute()Sie auch, dass Sie ohnehin vom UI-Thread aus aufrufen müssen, wodurch diese Option für den Anwendungsfall unbrauchbar wird, Code auf dem UI-Thread einfach aus einem Hintergrund-Thread auszuführen, es sei denn, Sie verschieben Ihre gesamte Hintergrundarbeit in den UI-Thread doInBackground()und verwenden ihn AsyncTaskordnungsgemäß.
Kabuko
@kabuko Wie kann ich überprüfen, ob ich AsyncTaskvom UI-Thread aus anrufe ?
Neil Galiaskarov
@ NeilGaliaskarov Dies sieht aus wie eine solide Option: stackoverflow.com/a/7897562/1839500
Dick Lucas
1
@ NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
Ban-Geoengineering
1
@ NeilGaliaskarov für Versionen größer oder gleich M verwendenLooper.getMainLooper().isCurrentThread
Balu Sangem
251

Ich mag den von HPP Kommentar , er kann überall ohne Parameter verwendet werden:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
pomber
quelle
2
Wie effizient ist das? Ist es dasselbe wie bei anderen Optionen?
EmmanuelMess
59

Es gibt einen vierten Weg mit Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
vasart
quelle
56
Sie sollten damit vorsichtig sein. Wenn Sie einen Handler in einem Nicht-UI-Thread erstellen, werden Nachrichten an den Nicht-UI-Thread gesendet. Ein Handler sendet standardmäßig eine Nachricht an den Thread, in dem sie erstellt wurde.
Lujop
107
Die Ausführung auf dem Haupt-UI-Thread do new Handler(Looper.getMainLooper()).post(r)ist die bevorzugte Methode, um Looper.getMainLooper()einen statischen Aufruf von main durchzuführen , während postOnUiThread()eine Instanz von MainActivityin scope vorhanden sein muss.
HPP
1
@HPP Ich kannte diese Methode nicht, wird eine großartige Möglichkeit sein, wenn Sie weder eine Ansicht noch eine Aktivität haben. Funktioniert super! Vielen Dank!
Sulfkain
@lujop Gleiches gilt für die Rückrufmethode onPreExecute von AsyncTask.
Sreekanth Karumanaghat
18

Die Antwort von Pomber ist akzeptabel, aber ich bin kein großer Fan davon, immer wieder neue Objekte zu erstellen. Die besten Lösungen sind immer diejenigen, die versuchen, das Gedächtnisproblem zu verringern. Ja, es gibt eine automatische Speicherbereinigung, aber die Speichererhaltung auf einem mobilen Gerät fällt unter die Grenzen der Best Practice. Der folgende Code aktualisiert eine Textansicht in einem Dienst.

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

}

Es kann von überall wie folgt verwendet werden:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Joe
quelle
7
+1 für die Erwähnung von GC und Objekterstellung. Ich stimme jedoch nicht unbedingt zu The best solutions are always the ones that try to mitigate memory hog. Es gibt viele andere Kriterien für best, und dies hat einen leichten Geruch von premature optimization. Das heißt, wenn Sie nicht wissen, dass Sie es so nennen, dass die Anzahl der erstellten Objekte ein Problem darstellt (im Vergleich zu den zehntausend anderen Methoden, mit denen Ihre App wahrscheinlich Müll erzeugt), ist es möglicherweise bestam einfachsten zu schreiben (am einfachsten zu verstehen) ) Code und fahren Sie mit einer anderen Aufgabe fort.
ToolmakerSteve
2
Übrigens, textViewUpdaterHandlersollte besser so etwas wie uiHandleroder genannt werden mainHandler, da es im Allgemeinen für jeden Beitrag zum Haupt-UI-Thread nützlich ist. Es ist überhaupt nicht an Ihre TextViewUpdater-Klasse gebunden. Ich würde es vom Rest dieses Codes entfernen und klarstellen, dass es an anderer Stelle verwendet werden kann ... Der Rest des Codes ist zweifelhaft, da Sie, um zu vermeiden, dass ein Objekt dynamisch erstellt wird, einen einzelnen Aufruf unterbrechen zwei Schritte setTextund post, die auf einem langlebigen Objekt beruhen, das Sie als temporäres verwenden. Unnötige Komplexität und nicht threadsicher. Nicht leicht zu pflegen.
ToolmakerSteve
Wenn Sie wirklich eine Situation haben, in der dies so oft aufgerufen wird, dass es sich lohnt, zwischenzuspeichern uiHandlerund textViewUpdaterdann Ihre Klasse zu verbessern, public void setText(String txt, Handler uiHandler)indem Sie zur Methodenzeile wechseln und diese hinzufügen. uiHandler.post(this); Dann kann der Aufrufer in einem Schritt Folgendes tun : textViewUpdater.setText("Hello", uiHandler);. Wenn die Methode in Zukunft threadsicher sein muss, kann sie ihre Anweisungen in eine Sperre einschließen uiHandler, und der Aufrufer bleibt unverändert.
ToolmakerSteve
Ich bin mir ziemlich sicher, dass Sie ein Runnable nur einmal ausführen können. Was dich absolut kaputt macht, was insgesamt schön ist.
Nativ
@Nativ Nein, Runnable kann mehrmals ausgeführt werden. Ein Thread kann nicht.
Zbyszek
8

Ab Android P können Sie verwenden getMainExecutor():

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

Aus den Android-Entwicklerdokumenten :

Geben Sie einen Executor zurück, der in der Warteschlange befindliche Aufgaben auf dem diesem Kontext zugeordneten Hauptthread ausführt. Dies ist der Thread, der zum Versenden von Aufrufen an Anwendungskomponenten (Aktivitäten, Dienste usw.) verwendet wird.

Aus dem CommonsBlog :

Sie können getMainExecutor () im Kontext aufrufen, um einen Executor abzurufen, der seine Jobs im Hauptanwendungsthread ausführt. Es gibt andere Möglichkeiten, dies mithilfe von Looper und einer benutzerdefinierten Executor-Implementierung zu erreichen. Dies ist jedoch einfacher.

Dick Lucas
quelle
7

Wenn Sie in Fragment verwenden müssen, sollten Sie verwenden

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

anstatt

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

Weil es in einigen Situationen wie dem Pager-Fragment eine Nullzeiger-Ausnahme gibt

Beyaz
quelle
1

Hallo Leute, dies ist eine grundlegende Frage, die ich weg erzähle

Verwenden Sie Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
Raghu Kambaa
quelle
Gibt es einen Grund, warum Sie sich für den Allzweck-Handler über runOnUiThread entschieden haben?
ThePartyTurtle
Dies gibt ein, java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()wenn es nicht vom UI-Thread aufgerufen wird.
Roc Boronat