Podstawy Androida: uruchamianie kodu w wątku interfejsu użytkownika

450

Czy z punktu widzenia uruchamiania kodu w wątku interfejsu użytkownika występuje jakaś różnica między:

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

lub

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

i

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
źródło
Aby wyjaśnić moje pytanie: podejrzewałem, że kod ten został wywołany z wątku usługi, zwykle ze nasłuchiwania. Przypuszczałem też, że jest ciężka praca do wykonania w funkcji doInBackground () AsynkTask lub w nowym zadaniu (...) wywołanym przed dwoma pierwszymi fragmentami. W każdym razie onPostExecute () z AsyncTask jest umieszczany na końcu kolejki zdarzeń, prawda?
Luky

Odpowiedzi:

288

Żadne z nich nie jest dokładnie takie samo, chociaż wszystkie będą miały taki sam efekt netto.

Różnica między pierwszym a drugim polega na tym, że jeśli zdarzy ci się być w głównym wątku aplikacji podczas wykonywania kodu, pierwszy ( runOnUiThread()) wykona Runnablenatychmiast. Drugi ( post()) zawsze umieszcza Runnablekoniec kolejki zdarzeń, nawet jeśli jesteś już w głównym wątku aplikacji.

Trzeci, zakładając, że utworzysz i wykonasz instancję BackgroundTask, będzie marnował dużo czasu na pobieranie wątku z puli wątków, aby wykonać domyślny brak operacji doInBackground(), zanim ostatecznie zrobisz to, co wynosi post(). Jest to zdecydowanie najmniej efektywny z tych trzech. Użyj, AsyncTaskjeśli faktycznie masz pracę do zrobienia w wątku w tle, nie tylko na użytek onPostExecute().

CommonsWare
źródło
27
Zauważ też, że AsyncTask.execute()wymaga to i tak wywoływania z wątku interfejsu użytkownika, co czyni tę opcję bezużyteczną w przypadku użycia po prostu uruchomienia kodu w wątku interfejsu z wątku tła, chyba że przeniesiesz całą pracę w tle do doInBackground()i będziesz używać AsyncTaskpoprawnie.
kabuko
@kabuko jak mogę sprawdzić, czy dzwonię AsyncTaskz wątku interfejsu użytkownika?
Neil Galiaskarov,
@NeilGaliaskarov Wygląda to na solidną opcję: stackoverflow.com/a/7897562/1839500
Dick Lucas
1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-geoinżynieria
1
@NeilGaliaskarov dla wersji większych lub równych MLooper.getMainLooper().isCurrentThread
Balu Sangem
252

Podoba mi się ten z komentarza HPP , można go używać w dowolnym miejscu bez żadnego parametru:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
pomber
źródło
2
Jak to jest wydajne? Czy to samo co inne opcje?
EmmanuelMess,
59

Istnieje czwarty sposób użycia Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
Vasart
źródło
56
Powinieneś być z tym ostrożny. Ponieważ jeśli utworzysz moduł obsługi w wątku innym niż interfejs użytkownika, będziesz publikować wiadomości w wątku innym niż interfejs użytkownika. Program obsługi domyślnie wysyła wiadomość do wątku, w którym został utworzony.
lujop 17.03.13
108
do wykonania w głównym wątku interfejsu użytkownika do new Handler(Looper.getMainLooper()).post(r), co jest preferowanym sposobem, ponieważ Looper.getMainLooper()wykonuje statyczne wywołanie main, podczas gdy postOnUiThread()musi mieć instancję MainActivityw zakresie.
HPP
1
@HPP Nie znałem tej metody, będzie świetnym sposobem, gdy nie masz ani Aktywności, ani Widoku. Działa świetnie! dziękuję bardzo bardzo bardzo!
Sulfkain
@lujop To samo dotyczy metody wywołania zwrotnego onPreExecute w AsyncTask.
Sreekanth Karumanaghat
18

Odpowiedź Pomber jest do zaakceptowania, jednak nie jestem wielkim fanem wielokrotnego tworzenia nowych obiektów. Najlepszymi rozwiązaniami są zawsze te, które próbują złagodzić problem pamięci. Tak, istnieje automatyczne zbieranie śmieci, ale zachowanie pamięci w urządzeniu mobilnym mieści się w zakresie najlepszych praktyk. Poniższy kod aktualizuje TextView w usłudze.

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

}

Można go używać z dowolnego miejsca takiego:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Joe
źródło
7
+1 za wzmiankę o GC i tworzeniu obiektów. JEDNAK niekoniecznie się z tym zgadzam The best solutions are always the ones that try to mitigate memory hog. Istnieje wiele innych kryteriów best, a to ma delikatny zapach premature optimization. Oznacza to, że jeśli nie wiesz, że nazywasz to wystarczająco, że liczba utworzonych obiektów stanowi problem (w porównaniu z dziesięcioma tysiącami innych sposobów, w jaki twoja aplikacja prawdopodobnie tworzy śmieci), to może być bestnapisanie najprostszego (najłatwiejszego do zrozumienia ) i przejdź do innego zadania.
ToolmakerSteve
2
BTW, textViewUpdaterHandlerlepiej byłoby nazwać coś podobnego uiHandlerlub mainHandler, ponieważ jest ogólnie przydatne dla każdego posta w głównym wątku interfejsu użytkownika; wcale nie jest powiązany z twoją klasą TextViewUpdater. Odsunąłbym go od reszty tego kodu i wyjaśniłbym, że można go użyć gdzie indziej ... Reszta kodu jest wątpliwa, ponieważ aby uniknąć dynamicznego tworzenia jednego obiektu, przerywasz to, co może być pojedynczym wywołaniem dwa kroki setTexti postpolegają na długotrwałym obiekcie, którego używasz jako tymczasowego. Niepotrzebna złożoność i bezpieczeństwo wątków. Niełatwe w utrzymaniu.
ToolmakerSteve
Jeśli rzeczywiście do czynienia z sytuacją, gdzie ten nazywa się tak wiele razy, że warto buforowanie uiHandleri textViewUpdater, a następnie poprawić swoją klasę przez zmianę public void setText(String txt, Handler uiHandler)oraz dodanie metodą liniową uiHandler.post(this); Następnie rozmówcy można zrobić w jednym kroku: textViewUpdater.setText("Hello", uiHandler);. Następnie, jeśli w przyszłości ma być bezpieczny wątek, metoda może owinąć swoje instrukcje wewnątrz blokady uiHandler, a osoba dzwoniąca pozostaje niezmieniona.
ToolmakerSteve
Jestem pewien, że możesz uruchomić Runnable tylko raz. Co absolutnie łamie ci pomysł, co ogólnie jest miłe.
Nativ
@Nativ Nie, Runnable może być uruchamiany wiele razy. Wątek nie może.
Zbyszek
8

Od Androida P możesz używać getMainExecutor():

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

Z dokumentacji dla programistów Androida :

Zwraca Executora, który uruchomi kolejkowane zadania w głównym wątku powiązanym z tym kontekstem. Jest to wątek używany do wysyłania wywołań do komponentów aplikacji (działań, usług itp.).

Z CommonsBlog :

Możesz wywołać metodę getMainExecutor () w kontekście, aby uzyskać executor, który wykona swoje zadania w głównym wątku aplikacji. Istnieją inne sposoby osiągnięcia tego celu przy użyciu Looper i niestandardowej implementacji Executora, ale jest to prostsze.

Dick Lucas
źródło
7

Jeśli potrzebujesz użyć we fragmencie, powinieneś użyć

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

zamiast

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

Ponieważ w niektórych sytuacjach, takich jak fragment pagera, wystąpi wyjątek wskaźnika zerowego

Beyaz
źródło
1

cześć chłopaki, to jest podstawowe pytanie, jak mówię

użyj Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
raghu kambaa
źródło
Czy jest jakiś powód, dla którego zdecydowałeś się korzystać z modułu obsługi ogólnego przeznaczenia zamiast runOnUiThread?
ThePartyTurtle
Daje to, java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()jeśli nie jest wywoływane z wątku interfejsu użytkownika.
Roc Boronat,