Nie można utworzyć modułu obsługi w wątku, który nie wywołał Looper.prepare ()

981

Co oznacza następujący wyjątek; jak mogę to naprawić?

To jest kod:

Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);

To jest wyjątek:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
     at android.os.Handler.<init>(Handler.java:121)
     at android.widget.Toast.<init>(Toast.java:68)
     at android.widget.Toast.makeText(Toast.java:231)
Michał
źródło
8
sprawdź tę bibliotekę compile 'com.shamanland:xdroid-toaster:0.0.5', nie wymaga runOnUiThread()ani Contextzmiennej, cała rutyna zniknęła! wystarczy przywołać Toaster.toast(R.string.my_msg);tutaj jest przykład: github.com/shamanland/xdroid-toaster-example
Oleksii K.
120
Co za głupi komunikat o błędzie! Mogłoby to być tak proste, jak - nie można wywołać tego z wątku innego niż interfejs użytkownika, tak jak w przypadku dotknięcia widoków z wątku innego niż interfejs użytkownika.
Dheeraj Bhaskar
11
Dla tych, którzy otrzymują ten sam komunikat wyjątku z innego kodu: Komunikat wyjątku oznacza, że ​​wywołujesz kod za pośrednictwem wątku, który nie przygotował Looper. Normalnie oznacza to, że nie dzwonisz, jeśli z wątku interfejsu użytkownika, ale powinieneś (przypadek OP) - normalny wątek nie przygotowuje Looper, ale wątek interfejsu użytkownika zawsze.
Helin Wang
@OleksiiKropachov implementacja wspomnianej biblioteki jest bardzo podobna do wykonywania runOnUiThread ().
Helin Wang
tak, ale jest to bardzo przydatne opakowanie
Oleksii K.

Odpowiedzi:

696

Dzwonisz z wątku roboczego. Musisz wywoływać Toast.makeText()(i większość innych funkcji związanych z interfejsem użytkownika) z głównego wątku. Możesz na przykład użyć modułu obsługi.

Wyszukaj temat Komunikacja z wątkiem interfejsu użytkownika w dokumentacji. W skrócie:

// Set this up in the UI thread.

mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message message) {
        // This is where you do your work in the UI thread.
        // Your worker tells you in the message what to do.
    }
};

void workerThread() {
    // And this is how you call it from the worker thread:
    Message message = mHandler.obtainMessage(command, parameter);
    message.sendToTarget();
}

Inne opcje:

Możesz użyć AsyncTask , który działa dobrze dla większości rzeczy działających w tle. Ma zaczepy, które można zadzwonić, aby wskazać postęp i kiedy to się skończy.

Możesz także użyć Activity.runOnUiThread () .

EboMike
źródło
co z pierwotnym problemem (nie chodziło o AlertDialog)?
Ivan G.
5
Dodając moje dwa centy do tego, co powiedziała Cleggy. Lepiej byłoby przedstawić krótką prezentację tego, co masz na myśli (jakkolwiek wymyślono), ponieważ zakodowany przykład często mówi sam za siebie.
cdata
5
na pełną odpowiedź technicznym zobaczyć ten prasanta-paul.blogspot.kr/2013/09/...
tony9099
3
W prawie wszystkich językach programowania AFAIK, które obsługują GUI, jeśli aktualizujesz / zmieniasz / wyświetlasz / współdziałasz bezpośrednio z GUI, należy to zrobić w głównym wątku programu.
Ahmed
(and most other functions dealing with the UI)Przykładem funkcji interfejsu użytkownika używanej z tła jest android.support.design.widget.Snackbar- jej funkcjonalność nie jest zmniejszona, gdy nie jest wywoływana z wątku interfejsu użytkownika.
Scruffy
852

Musisz zadzwonić Toast.makeText(...)z wątku interfejsu użytkownika:

activity.runOnUiThread(new Runnable() {
  public void run() {
    Toast.makeText(activity, "Hello", Toast.LENGTH_SHORT).show();
  }
});

Jest to wklejone z innej (duplikatu) odpowiedzi SO .

Jacob Marble
źródło
13
Świetna odpowiedź. Przez pewien czas byłem zdezorientowany. Dla przypomnienia, nie potrzebowałem tej aktywności. przed runOnUiThread.
Cen92
448

AKTUALIZACJA - 2016

Najlepszą alternatywą jest użycie RxAndroid(specyficzne dla wiązania RxJava) dla Pw MVPcelu przejęcie fo danych.

Zacznij od powrotu Observablez istniejącej metody.

private Observable<PojoObject> getObservableItems() {
    return Observable.create(subscriber -> {

        for (PojoObject pojoObject: pojoObjects) {
            subscriber.onNext(pojoObject);
        }
        subscriber.onCompleted();
    });
}

Użyj tego obserwowalnego w ten sposób -

getObservableItems().
subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).
subscribe(new Observer<PojoObject> () {
    @Override
    public void onCompleted() {
        // Print Toast on completion
    }

    @Override
    public void onError(Throwable e) {}

    @Override
    public void onNext(PojoObject pojoObject) {
        // Show Progress
    }
});
}

-------------------------------------------------- -------------------------------------------------- ------------------------------

Wiem, że jestem trochę spóźniony, ale proszę bardzo. Android zasadniczo działa na dwóch typach wątków, a mianowicie wątku interfejsu użytkownika i wątku tła . Według dokumentacji Androida -

Nie można uzyskać dostępu do zestawu narzędzi interfejsu użytkownika Androida spoza wątku interfejsu użytkownika, aby rozwiązać ten problem, system Android oferuje kilka sposobów uzyskiwania dostępu do wątku interfejsu użytkownika z innych wątków. Oto lista metod, które mogą pomóc:

Activity.runOnUiThread(Runnable)  
View.post(Runnable)  
View.postDelayed(Runnable, long)

Teraz istnieją różne metody rozwiązania tego problemu.

Wyjaśnię to przez przykład kodu:

runOnUiThread

new Thread()
{
    public void run()
    {
        myactivity.this.runOnUiThread(new Runnable()
        {
            public void run()
            {
                //Do your UI operations like dialog opening or Toast here
            }
        });
    }
}.start();

LOOPER

Klasa używana do uruchamiania pętli komunikatów dla wątku. Wątki domyślnie nie są powiązane z pętlą komunikatów; aby go utworzyć, wywołaj funkcję prep () w wątku, który ma uruchomić pętlę, a następnie pętlę (), aby przetwarzała komunikaty do momentu zatrzymania pętli.

class LooperThread extends Thread {
    public Handler mHandler;

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

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

        Looper.loop();
    }
}

AsyncTask

AsyncTask umożliwia wykonywanie pracy asynchronicznej w interfejsie użytkownika. Wykonuje operacje blokowania w wątku roboczym, a następnie publikuje wyniki w wątku interfejsu użytkownika, bez konieczności samodzielnego obsługiwania wątków i / lub procedur obsługi.

public void onClick(View v) {
    new CustomTask().execute((Void[])null);
}


private class CustomTask extends AsyncTask<Void, Void, Void> {

    protected Void doInBackground(Void... param) {
        //Do some work
        return null;
    }

    protected void onPostExecute(Void param) {
        //Print Toast or open dialog
    }
}

Treser

Moduł obsługi umożliwia wysyłanie i przetwarzanie obiektów Message i Runnable powiązanych z MessageQueue wątku.

Message msg = new Message();


new Thread()
{
    public void run()
    {
        msg.arg1=1;
        handler.sendMessage(msg);
    }
}.start();



Handler handler = new Handler(new Handler.Callback() {

    @Override
    public boolean handleMessage(Message msg) {
        if(msg.arg1==1)
        {
            //Print Toast or open dialog        
        }
        return false;
    }
});
mjosh
źródło
7
To jest dokładnie to, czego szukałem. Zwłaszcza pierwszy przykład zrunOnUiThread
Navin
5
Dzięki, 5 lat programowania na Androida i nigdy nie wiedziałem, że Viewma też metody post(Runnable)i postDelayed(Runnable, long)! Tyle handlerów na próżno. :)
Fenix ​​Voltres
dla tych, którzy są zdezorientowani przykładem Handler: z jakim wątkiem wiąże się „new Handler (callback)”? Jest powiązany z wątkiem, który utworzył moduł obsługi.
Helin Wang
1
Dlaczego to najlepsza alternatywa ?
IgorGanapolsky
Używam doInBackground i chcę odzyskać ArrayList, ale zawsze pojawia się błąd: nie można utworzyć modułu obsługi w wątku, który nie wywołał Looper.prepare (). Zobacz, oto moje pytanie stackoverflow.com/questions/45562615/... ale nie mogę znaleźć rozwiązania z tej odpowiedzi tutaj
WeSt
120

Toast.makeText()powinien być wywoływany tylko z wątku Main / UI. Looper.getMainLooper () pomaga Ci to osiągnąć:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);
    }
});

Zaletą tej metody jest to, że można jej używać bez działania lub kontekstu.

Ayaz Alifov
źródło
2
Dzięki, inne odpowiedzi nie działały dla mnie. Używam biblioteki cukru w ​​bibliotece do zarządzania trwałością. I w środku nie mam aktywności. Ale to działa cudownie
cabaji99
91

Spróbuj tego, gdy zobaczysz wyjątek runtimeException ze względu na to, że Looper nie został przygotowany przed obsługą

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

handler.postDelayed(new Runnable() {
  @Override
  public void run() {
  // Run your task here
  }
}, 1000 );
Khulja Sim Sim
źródło
Handler to klasy abstrakcyjne. to się nie kompiluje
Stealth Rabbi
2
@StealthRabbi importer obsługi z prawidłowej przestrzeni nazw, tj.android.os.Handler
NightFury
To może nie być problem. Looper może nie istnieć w klasie wywołującej kropka.
IgorGanapolsky
42

Natknąłem się na ten sam problem i oto jak go naprawiłem:

private final class UIHandler extends Handler
{
    public static final int DISPLAY_UI_TOAST = 0;
    public static final int DISPLAY_UI_DIALOG = 1;

    public UIHandler(Looper looper)
    {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg)
    {
        switch(msg.what)
        {
        case UIHandler.DISPLAY_UI_TOAST:
        {
            Context context = getApplicationContext();
            Toast t = Toast.makeText(context, (String)msg.obj, Toast.LENGTH_LONG);
            t.show();
        }
        case UIHandler.DISPLAY_UI_DIALOG:
            //TBD
        default:
            break;
        }
    }
}

protected void handleUIRequest(String message)
{
    Message msg = uiHandler.obtainMessage(UIHandler.DISPLAY_UI_TOAST);
    msg.obj = message;
    uiHandler.sendMessage(msg);
}

Aby utworzyć UIHandler, musisz wykonać następujące czynności:

    HandlerThread uiThread = new HandlerThread("UIHandler");
    uiThread.start();
    uiHandler = new UIHandler((HandlerThread) uiThread.getLooper());

Mam nadzieję że to pomoże.

ChicoBird
źródło
Próbowałem użyć Twojego kodu, ale zgubiłem się i nie jestem pewien, jak zadzwonić z onCreate methodAsyncTask lub z AsyncTask. W mojej sytuacji możesz napisać cały kod, aby dowiedzieć się, jak to działa?
Nick Kahn
2
Czy ten ostatni wiersz nie powinien przeczytać uiHandler = new UIHandler(uiThread.getLooper()); ?
Beer Me
36

Przyczyna błędu:

Wątki robocze są przeznaczone do wykonywania zadań w tle i nie można wyświetlić niczego w interfejsie użytkownika w wątku roboczym, chyba że wywołasz metodę taką jak runOnUiThread . Jeśli spróbujesz wyświetlić cokolwiek w wątku interfejsu użytkownika bez wywoływania polecenia runOnUiThread, pojawi się znak java.lang.RuntimeException.

Jeśli więc activitydzwonisz Toast.makeText()z wątku roboczego, wykonaj następujące czynności:

runOnUiThread(new Runnable() 
{
   public void run() 
   {
      Toast toast = Toast.makeText(getApplicationContext(), "Something", Toast.LENGTH_SHORT).show();    
   }
}); 

Powyższy kod gwarantuje, że wyświetlasz komunikat Toast, UI threadponieważ wywołujesz go w runOnUiThreadmetodzie. Więc nie więcej java.lang.RuntimeException.

Mój Boże
źródło
23

To jest to co zrobiłem.

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Toast(...);
    }
});

Komponenty wizualne są „zablokowane” na zmiany z zewnętrznych wątków. Ponieważ toast pokazuje rzeczy na ekranie głównym zarządzanym przez główny wątek, musisz uruchomić ten kod w tym wątku. Mam nadzieję, że to pomoże :)

Eiran
źródło
Użyłem tej samej metody. Czy to jednak pozostawia otwartą możliwość wycieków, ponieważ anonimowa klasa wewnętrzna Runnable będzie zawierać ukryte odniesienie do działania?
Peter G. Williams
1
To jest w porządku :) po prostu użyj getApplicationContext () lub czegoś takiego, aby być bezpiecznym. chociaż nigdy nie miałem żadnych problemów z tym kodem, o którym wiem
eiran
22

Otrzymywałem ten błąd, dopóki nie wykonałem następujących czynności.

public void somethingHappened(final Context context)
{
    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(
        new Runnable()
        {
            @Override
            public void run()
            {
                Toast.makeText(context, "Something happened.", Toast.LENGTH_SHORT).show();
            }
        }
    );
}

I uczyniło to z klasy singleton:

public enum Toaster {
    INSTANCE;

    private final Handler handler = new Handler(Looper.getMainLooper());

    public void postMessage(final String message) {
        handler.post(
            new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(ApplicationHolder.INSTANCE.getCustomApplication(), message, Toast.LENGTH_SHORT)
                        .show();
                }
            }
        );
    }

}
EpicPandaForce
źródło
Gdzie używasz Toastera ? W pierwszym fragmencie nie jest używany ...
IgorGanapolsky
1
była to wygodna klasa, którą lubiłem Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(długo wiem! zmniejszyliśmy to później), chociaż od jakiegoś czasu nie korzystałem z tostów
EpicPandaForce
Więc co ApplicationHolder.INSTANCEocenia?
IgorGanapolsky
CustomApplicationUstawiona zmienna statyczna CustomApplication.onCreate(), biorąc pod uwagę, że aplikacja zawsze istnieje, dopóki proces istnieje, kontekst ten może być używany globalnie
EpicPandaForce
11
 runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(mContext, "Message", Toast.LENGTH_SHORT).show();
            }
        });
Coldfin Lab
źródło
2
To zadziałało dla mnie i używam lambdarunOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); });
Black_Zerg
11

Cudowne rozwiązanie Kotlin:

runOnUiThread {
    // Add your ui thread code here
}
jungledev
źródło
4
runOnUiThreadjest częścią Activity tj.activity?.runOnUiThread { ... }
AtomicStrongForce
9

Wynika to z faktu, że Toast.makeText () dzwoni z wątku roboczego. Powinien być wywołany z głównego wątku interfejsu użytkownika w ten sposób

runOnUiThread(new Runnable() {
      public void run() {
        Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);
      }
 });
Biswajit Karmakar
źródło
8

Odpowiedź ChicoBirda zadziałała dla mnie. Jedyną zmianą, którą wprowadziłem, było stworzenie UIHandler, gdzie musiałem to zrobić

HandlerThread uiThread = new HandlerThread("UIHandler");

Eclipse odmówił przyjęcia czegokolwiek innego. Wydaje mi się, że ma to sens.

Jest też uiHandlerwyraźnie zdefiniowana gdzieś globalna klasa. Nadal nie twierdzę, że rozumiem, jak Android to robi i co się dzieje, ale cieszę się, że to działa. Teraz przejdę do studiowania i zobaczę, czy mogę zrozumieć, co robi Android i dlaczego trzeba przejść przez wszystkie te pętle. Dzięki za pomoc ChicoBird.

Brian Reinhold
źródło
6

pierwsze połączenie, Looper.prepare()a następnie połączenie Toast.makeText().show()ostatnie Looper.loop()jak:

Looper.prepare() // to be able to make toast
Toast.makeText(context, "not connected", Toast.LENGTH_LONG).show()
Looper.loop()
Hasan A Yousef
źródło
Dlaczego ta odpowiedź jest niedoceniana?
DkPathak
5

Dla użytkowników Rxjava i RxAndroid:

public static void shortToast(String msg) {
    Observable.just(msg)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(message -> {
                Toast.makeText(App.getInstance(), message, Toast.LENGTH_SHORT).show();
            });
}
Geng Jiawen
źródło
Te nawiasy nie są potrzebne
Borja
4

Wystąpił ten sam problem, gdy moje oddzwaniania próbowały wyświetlić okno dialogowe.

Rozwiązałem go za pomocą dedykowanych metod w działaniu - na poziomie elementu instancji działania - które go wykorzystująrunOnUiThread(..)

public void showAuthProgressDialog() {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mAuthProgressDialog = DialogUtil.getVisibleProgressDialog(SignInActivity.this, "Loading ...");
        }
    });
}

public void dismissAuthProgressDialog() {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (mAuthProgressDialog == null || ! mAuthProgressDialog.isShowing()) {
                return;
            }
            mAuthProgressDialog.dismiss();
        }
    });
}
Gene Bo
źródło
2
Handler handler2;  
HandlerThread handlerThread=new HandlerThread("second_thread");
handlerThread.start();
handler2=new Handler(handlerThread.getLooper());

Teraz handler2 użyje innego wątku do obsługi wiadomości niż główny wątek.

Tarasantan
źródło
1

Aby wyświetlić okno dialogowe lub toster w wątku, najbardziej zwięzłym sposobem jest użycie obiektu Activity.

Na przykład:

new Thread(new Runnable() {
    @Override
    public void run() {
        myActivity.runOnUiThread(new Runnable() {
            public void run() {
                myActivity.this.processingWaitDialog = new ProgressDialog(myActivity.this.getContext());
                myActivity.this.processingWaitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                myActivity.this.processingWaitDialog.setMessage("abc");
                myActivity.this.processingWaitDialog.setIndeterminate(true);
                myActivity.this.processingWaitDialog.show();
            }
        });
        expenseClassify.serverPost(
                new AsyncOperationCallback() {
                    public void operationCompleted(Object sender) {
                        myActivity.runOnUiThread(new Runnable() {
                            public void run() {
                                if (myActivity.this.processingWaitDialog != null 
                                        && myActivity.this.processingWaitDialog.isShowing()) {
                                    myActivity.this.processingWaitDialog.dismiss();
                                    myActivity.this.processingWaitDialog = null;
                                }
                            }
                        }); // .runOnUiThread(new Runnable()
...
Liwen Zhao
źródło
0

Tosty, AlertDialogs musi uruchomić na wątków UI, można użyć Asynctask je właściwie wykorzystać w android development.but niektórych przypadkach musimy dostosować przerw czasowych, więc używamy wątki , ale w wątkach Nie możemy używać tosty, Alertdialogs jak używamy w AsyncTask.So Musimy oddzielić Handler dla popup tych.

public void onSigned() {
    Thread thread = new Thread(){
        @Override
        public void run() {
            try{
                sleep(3000);
                Message message = new Message();
                message.what = 2;
                handler.sendMessage(message);
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

w powyższym przykładzie chcę uśpić mój wątek za 3 sekundy, a następnie chcę pokazać komunikat Toast, za to w twoim module obsługi wątku głównego .

handler = new Handler() {
       public void handleMessage(Message msg) {
           switch(msg.what){
              case 1:
              Toast.makeText(getActivity(),"cool",Toast.LENGTH_SHORT).show();
              break;
           }
           super.handleMessage(msg);
       }
};

Użyłem tu skrzynki rozdzielczej, ponieważ jeśli chcesz pokazać inny komunikat w ten sam sposób, możesz użyć skrzynki przełączników w klasie Handler ... mam nadzieję, że to ci pomoże

Ashana.Jackol
źródło
0

Zwykle dzieje się tak, gdy coś w głównym wątku jest wywoływane z dowolnego wątku tła. Spójrzmy na przykład na przykład.

private class MyTask extends AsyncTask<Void, Void, Void> {


@Override
protected Void doInBackground(Void... voids) {
        textView.setText("Any Text");
        return null;
    }
}

W powyższym przykładzie ustawiamy tekst w widoku tekstu, który znajduje się w głównym wątku interfejsu użytkownika z metody doInBackground (), która działa tylko na wątku roboczym.

Prashant Paliwal
źródło
0

Miałem ten sam problem i naprawiłem go po prostu poprzez włączenie Toast w funkcji zastępowania onPostExecute () Asynctask <> i zadziałało.

alibabaei12
źródło
-2

używam następującego kodu, aby wyświetlić wiadomość z kontekstu innego niż główny wątek,

@FunctionalInterface
public interface IShowMessage {
    Context getContext();

    default void showMessage(String message) {
        final Thread mThread = new Thread() {
            @Override
            public void run() {
                try {
                    Looper.prepare();
                    Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
                    Looper.loop();
                } catch (Exception error) {
                    error.printStackTrace();
                    Log.e("IShowMessage", error.getMessage());
                }
            }
        };
        mThread.start();
    }
}

następnie użyj następującego polecenia:

class myClass implements IShowMessage{

  showMessage("your message!");
 @Override
    public Context getContext() {
        return getApplicationContext();
    }
}
Mohamed.Abdo
źródło