Uruchamianie kodu w głównym wątku z innego wątku


326

W usłudze Androida utworzyłem wątki do wykonania zadania w tle.

Mam sytuację, w której wątek musi opublikować określone zadanie w kolejce komunikatów głównego wątku, na przykład a Runnable.

Czy istnieje sposób, aby uzyskać Handlerod głównego wątku i post Message/ Runnabledo niego z moim innym wątku?

Dzięki,


2
Możesz także użyć niestandardowego odbiornika transmisji .... wypróbuj moją odpowiedź tutaj, [Wewnętrzny odbiornik transmisji] [1] [1]: stackoverflow.com/a/22541324/1881527
Melbourne Lopes

Jest wiele sposobów. Oprócz odpowiedzi Davida i komentarza dzeikei w jego odpowiedzi (3) możesz użyć odbiornika rozgłoszeniowego lub (4) przekazać moduł obsługi dodatkami Intent służącymi do uruchomienia usługi, a następnie pobrać moduł obsługi głównego wątku wewnątrz usługi za pomocą getIntent ( ) .getExtras ().
Ashok Bijoy Debnath,

2
@ sazzad-hissain-khan, Dlaczego otagować to pytanie z 2012 roku głównie odpowiedziami w Javie za pomocą tagu kotlin?
Tenfour04,

Odpowiedzi:


617

UWAGA: Na tę odpowiedź zwrócono tak dużą uwagę, że muszę ją zaktualizować. Od czasu opublikowania oryginalnej odpowiedzi komentarz @dzeikei zyskał prawie tyle samo uwagi, co oryginalna odpowiedź. Oto 2 możliwe rozwiązania:

1. Jeśli wątek w tle ma odniesienie do Contextobiektu:

Upewnij się, że wątki procesu roboczego w tle mają dostęp do obiektu Context (może to być kontekst aplikacji lub kontekst usługi). Następnie po prostu zrób to w wątku procesu roboczego w tle:

// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());

Runnable myRunnable = new Runnable() {
    @Override 
    public void run() {....} // This is your code
};
mainHandler.post(myRunnable);

2. Jeśli wątek w tle nie ma (lub nie potrzebuje) Contextobiektu

(sugerowane przez @dzeikei):

// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(Looper.getMainLooper());

Runnable myRunnable = new Runnable() {
    @Override 
    public void run() {....} // This is your code
};
mainHandler.post(myRunnable);

1
Dzięki Davidowi, zadziałało to dla mnie, jeszcze jedna rzecz, jeśli mógłbyś mi pomóc, jeśli rozszerzę Handler i impl handleMessage (), czy uniemożliwiłoby to głównemu wątkowi obsługę jego komunikatów? to tylko pytanie z ciekawości ..
Ahmed,

2
Nie. Jeśli podklasujesz Handler(lub używasz Handler.Callbackinterfejsu), twoja handleMessage()metoda będzie wywoływana TYLKO dla wiadomości, które zostały opublikowane za pomocą twojego programu obsługi. Główny wątek używa innej procedury obsługi do wysyłania / przetwarzania wiadomości, więc nie ma konfliktu.
David Wasser

205
Wierzę, że nie będziesz potrzebować kontekstu, jeśli użyjeszHandler mainHandler = new Handler(Looper.getMainLooper());
dzeikei

6
Drobny punkt; twój kod nie idzie tam, gdzie ... obecnie jest. Powinno byćnew Runnable(){@Override public void run() {....}};
Richard Tingle,

1
@SagarDevanga To nie jest właściwe miejsce, aby zadać inne pytanie. Proszę zamieścić nowe pytanie, a nie komentarz do niezwiązanej odpowiedzi. W ten sposób uzyskasz lepszą i szybszą reakcję.
David Wasser

138

Jak komentator poniżej wskazał poprawnie, nie jest to ogólne rozwiązanie dla usług, tylko dla wątków uruchamianych z twojej aktywności (usługa może być takim wątkiem, ale nie wszystkie z nich są). Na skomplikowany temat komunikacji między usługami a usługami proszę przeczytać całą sekcję Usługi oficjalnego dokumentu - jest skomplikowana, więc opłaca się zrozumieć podstawy: http://developer.android.com/guide/components/services.html # Powiadomienia

Poniższa metoda może działać w najprostszych przypadkach.

Jeśli dobrze cię rozumiem, potrzebujesz kodu do wykonania w wątku GUI aplikacji (nie mogę myśleć o niczym innym, co nazywa się „głównym” wątkiem). W tym celu istnieje metoda Activity:

someActivity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
           //Your code to run in GUI thread here
        }//public void run() {
});

Dokument: http://developer.android.com/reference/android/app/Activity.html#runOnUiThread%28java.lang.Runnable%29

Mam nadzieję, że tego właśnie szukasz.


20
OP mówi, że prowadzi wątki w Service. Nie możesz użyć runOnUiThread()w Service. Ta odpowiedź jest myląca i nie odnosi się do zadanego pytania.
David Wasser

31

Istnieje inny prosty sposób, jeśli nie masz dostępu do kontekstu.

1). Utwórz moduł obsługi z głównego looper'a:

Handler uiHandler = new Handler(Looper.getMainLooper());

2). Zaimplementuj interfejs Runnable:

Runnable runnable = new Runnable() { // your code here }

3). Opublikuj Runnable w uiHandler:

uiHandler.post(runnable);

To wszystko ;-) Baw się dobrze z wątkami, ale nie zapomnij ich zsynchronizować.


29

Jeśli uruchamiasz kod w wątku, np. Opóźniasz jakieś działanie, musisz wywołać runOnUiThreadz kontekstu. Na przykład, jeśli kod znajduje się w MainActivityklasie, użyj tego:

MainActivity.this.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        myAction();
    }
});

Jeśli twoją metodę można wywołać albo z głównego (wątek interfejsu użytkownika), albo z innych wątków, musisz sprawdzić:

public void myMethod() {
   if( Looper.myLooper() == Looper.getMainLooper() ) {
       myAction();
   }
   else {

}

7
OP mówi, że prowadzi wątki w Service. Nie możesz użyć runOnUiThread()w Service.
David Wasser

@DavidWasser Czy jest to udokumentowane w dowolnym miejscu? Dokumenty dotyczące metod nie wspominają nic na ten temat. developer.android.com/reference/android/app/…
Greg Brown

1
@GregBrown Jak wskazałeś przez link do Activitydokumentacji, runOnUiThread()jest to metoda Activity. Nie jest to metoda Service, dlatego nie można jej użyć. Co może być bardziej zrozumiałe?
David Wasser

@DavidWasser Do przyjęcia. Nie pamiętam nawet, dlaczego zadałem teraz to pytanie (zostało opublikowane prawie rok temu).
Greg Brown,

24

Skondensowany blok kodu jest następujący:

   new Handler(Looper.getMainLooper()).post(new Runnable() {
       @Override
       public void run() {
           // things to do on the main thread
       }
   });

Nie obejmuje to przekazywania odwołania do działania lub odwołania do aplikacji.

Odpowiednik Kotlin:

    Handler(Looper.getMainLooper()).post(Runnable {
        // things to do on the main thread
    })

20

Wersje Kotlin

Kiedy jesteś na aktywności , użyj

runOnUiThread {
    //code that runs in main
}

Gdy masz kontekst działania , mContext następnie użyj

mContext.runOnUiThread {
    //code that runs in main
}

Jeśli jesteś w miejscu, w którym nie ma dostępnego kontekstu , użyj

Handler(Looper.getMainLooper()).post {  
    //code that runs in main
}

Nie jestem pewien, która to wersja Kotlina, ale musiałem dodać nawiasy klamrowe:runOnUiThread(){...}
Wex

5

Najprostszy sposób, zwłaszcza jeśli nie masz kontekstu, jeśli używasz RxAndroid, możesz:

AndroidSchedulers.mainThread().scheduleDirect {
    runCodeHere()
}

5

Bardziej precyzyjny kod Kotlin za pomocą programu obsługi:

Handler(Looper.getMainLooper()).post {  
 // your codes here run on main Thread
 }

4

Jedną z metod, o której mogę myśleć, jest:

1) Pozwól interfejsowi użytkownika powiązać się z usługą.
2) Podaj metodę podobną do poniższej, Binderktóra rejestruje twoje Handler:

public void registerHandler(Handler handler) {
    mHandler = handler;
}

3) W wątku interfejsu użytkownika wywołaj powyższą metodę po powiązaniu z usługą:

mBinder.registerHandler(new Handler());

4) Użyj modułu obsługi w wątku usługi, aby opublikować swoje zadanie:

mHandler.post(runnable);

3

HandlerThread jest lepszą opcją dla normalnych wątków Java w Androidzie.

  1. Utwórz HandlerThread i uruchom go
  2. Utwórz Handler za pomocą Looper z HandlerThread:requestHandler
  3. postRunnablezadanie narequestHandler

Komunikacja z wątkiem interfejsu użytkownika z HandlerThread

  1. Utwórz metodę Handlerwith Looperdla głównego wątku: responseHandleri przesłonięciahandleMessage
  2. Wewnątrz Runnablezadania innym wątku ( HandlerThreadw tym przypadku), zadzwoń sendMessagenaresponseHandler
  3. To sendMessagewywołanie wyniku handleMessagew responseHandler.
  4. Pobierz atrybuty Messagei przetworz je, zaktualizuj interfejs użytkownika

Przykład : aktualizacja TextViewdanych otrzymanych z usługi internetowej. Ponieważ usługa sieci web powinna być wywoływana w wątku innym niż interfejs użytkownika, utworzonym HandlerThreaddo działania w sieci. Po pobraniu treści z usługi internetowej wyślij wiadomość do głównego modułu obsługi wątku (wątku interfejsu użytkownika), Handlerktóry obsłuży komunikat i zaktualizuje interfejs użytkownika.

Przykładowy kod:

HandlerThread handlerThread = new HandlerThread("NetworkOperation");
handlerThread.start();
Handler requestHandler = new Handler(handlerThread.getLooper());

final Handler responseHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        txtView.setText((String) msg.obj);
    }
};

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Log.d("Runnable", "Before IO call");
            URL page = new URL("http://www.your_web_site.com/fetchData.jsp");
            StringBuffer text = new StringBuffer();
            HttpURLConnection conn = (HttpURLConnection) page.openConnection();
            conn.connect();
            InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
            BufferedReader buff = new BufferedReader(in);
            String line;
            while ((line = buff.readLine()) != null) {
                text.append(line + "\n");
            }
            Log.d("Runnable", "After IO call:"+ text.toString());
            Message msg = new Message();
            msg.obj = text.toString();
            responseHandler.sendMessage(msg);


        } catch (Exception err) {
            err.printStackTrace();
        }
    }
};
requestHandler.post(myRunnable);

Przydatne artykuły:

handlerthreads-and-dlaczego-you-should-be-using-them-in-your-android-apps

android-looper-handler-handlerthread-i


2

Wiem, że to stare pytanie, ale natknąłem się na główny wątek, którego używam zarówno w Kotlinie, jak i Javie. To może nie być najlepsze rozwiązanie dla usługi, ale wywołanie czegoś, co zmieni interfejs użytkownika wewnątrz fragmentu, jest niezwykle proste i oczywiste.

Java (8):

 getActivity().runOnUiThread(()->{
      //your main thread code
 });

Kotlin:

this.runOnUiThread {
     //your main thread code
}

1

Postępuj zgodnie z tą metodą. W ten sposób możesz po prostu zaktualizować interfejs użytkownika z wątku w tle. praca runOnUiThread w wątku głównym (UI). Myślę, że ten fragment kodu jest mniej złożony i łatwy, szczególnie dla początkujących.

AsyncTask.execute(new Runnable() {
            @Override
            public void run() {

            //code you want to run on the background
            someCode();

           //the code you want to run on main thread
 MainActivity.this.runOnUiThread(new Runnable() {

                    public void run() {

/*the code you want to run after the background operation otherwise they will executed earlier and give you an error*/
                        executeAfterOperation();

                   }
                });
            }
        });

w przypadku usługi

utwórz moduł obsługi w utworzonym pliku

 handler = new Handler();

następnie użyj tego w ten sposób

 private void runOnUiThread(Runnable runnable) {
        handler.post(runnable);
    }

Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie byłoby znacznie poprawić swoją długoterminową wartość pokazując dlaczego jest to dobre rozwiązanie problemu, a byłoby bardziej użyteczne dla czytelników przyszłości z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
iBug

1

w przypadku Kotlina można używać kontenów Anko :

aktualizacja

doAsync {
   ...
}

przestarzałe

async(UI) {
    // Code run on UI thread
    // Use ref() instead of this@MyActivity
}

1

Najbardziej przydatne jest zrobienie czegoś takiego:

import android.os.AsyncTask
import android.os.Handler
import android.os.Looper

object Dispatch {
    fun asyncOnBackground(call: ()->Unit) {
        AsyncTask.execute {
            call()
        }
    }

    fun asyncOnMain(call: ()->Unit) {
        Handler(Looper.getMainLooper()).post {
            call()
        }
    }
}

I po:

Dispatch.asyncOnBackground {
    val value = ...// super processing
    Dispatch.asyncOnMain { completion(value)}
}

0
public void mainWork() {
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //Add Your Code Here
        }
    });
}

Może to również działać bez problemu w klasie usług.


Chociaż ten kod może odpowiedzieć na pytanie, nadal możesz rozważyć dodanie kilku zdań wyjaśniających, ponieważ zwiększa to wartość Twojej odpowiedzi dla innych użytkowników!
MBT

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.