Handler innerhalb eines Threads, der Looper.prepare () nicht aufgerufen hat, kann nicht erstellt werden

981

Was bedeutet die folgende Ausnahme? wie kann ich es reparieren?

Dies ist der Code:

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

Dies ist die Ausnahme:

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)
Michael
quelle
8
Überprüfen Sie diese Bibliothek compile 'com.shamanland:xdroid-toaster:0.0.5', es ist nicht erforderlich runOnUiThread()oder Contextvariabel, alle Routine ist weg! Toaster.toast(R.string.my_msg);
Rufen Sie
120
Was für eine blöde Fehlermeldung! Es hätte so einfach sein können wie - kann dies nicht von einem Nicht-UI-Thread aus aufrufen, wie dies getan wird, wenn Ansichten von einem Nicht-UI-Thread berührt werden.
Dheeraj Bhaskar
11
Für diejenigen, die dieselbe Ausnahmemeldung von unterschiedlichem Code erhalten: Die Ausnahmemeldung bedeutet, dass Sie den Code über einen Thread aufrufen, der Looper nicht vorbereitet hat. Normalerweise bedeutet dies, dass Sie nicht anrufen, wenn Sie vom UI-Thread kommen, aber Sie sollten (Fall von OP) - ein normaler Thread bereitet Looper nicht vor, UI-Thread jedoch immer.
Helin Wang
@OleksiiKropachov Die Implementierung der von Ihnen erwähnten Bibliothek ist der Ausführung eines runOnUiThread () sehr ähnlich.
Helin Wang
Ja, aber es ist ein sehr nützlicher Wrapper
Oleksii K.

Antworten:

696

Sie rufen es von einem Arbeitsthread aus auf. Sie müssen Toast.makeText()(und die meisten anderen Funktionen, die sich mit der Benutzeroberfläche befassen) aus dem Hauptthread heraus aufrufen . Sie könnten beispielsweise einen Handler verwenden.

Nachschlagen Kommunikation mit dem UI - Thread in der Dokumentation. In einer Nussschale:

// 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();
}

Andere Optionen:

Sie können eine AsyncTask verwenden , die für die meisten Dinge, die im Hintergrund ausgeführt werden, gut funktioniert. Es hat Hooks, die Sie aufrufen können, um den Fortschritt anzuzeigen und wann es fertig ist.

Sie können auch Activity.runOnUiThread () verwenden .

EboMike
quelle
Was ist mit dem ursprünglichen Problem (es ging nicht um AlertDialog)?
Ivan G.
5
Ich füge nur meine zwei Cent zu dem hinzu, was Cleggy gesagt hat. Es wäre vorzuziehen, eine kurze Demonstration dessen zu geben, was Sie meinen (wie auch immer erfunden), da ein codiertes Beispiel oft Bände für sich selbst sprechen kann.
cdata
5
Eine vollständige technische Antwort finden Sie in diesem prasanta-paul.blogspot.kr/2013/09/…
tony9099
3
In fast allen AFAIK-Programmiersprachen, die die GUI unterstützen, sollte die direkte Aktualisierung / Änderung / Anzeige / Interaktion mit der GUI im Hauptthread des Programms erfolgen.
Ahmed
(and most other functions dealing with the UI)Ein Beispiel für eine UI-Funktion, die im Hintergrund verwendet werden kann, ist android.support.design.widget.Snackbar- ihre Funktionalität ist unvermindert, wenn nicht vom UI-Thread aus aufgerufen wird.
Scruffy
853

Sie müssen Toast.makeText(...)vom UI-Thread aus aufrufen :

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

Dies wird aus einer anderen (doppelten) SO-Antwort kopiert .

Jacob Marmor
quelle
13
Gute Antwort. Das hatte mich eine Weile verwirrt. Nur um zu beachten, ich brauchte die Aktivität nicht. vor runOnUiThread.
Cen92
448

UPDATE - 2016

Die beste Alternative ist die Verwendung RxAndroid(spezifische Bindungen für RxJava) für den Pin MVPfo Daten zu übernehmen.

Kehren Sie zunächst von ObservableIhrer vorhandenen Methode zurück.

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

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

Verwenden Sie dieses Observable wie folgt -

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

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

Ich weiß, ich bin etwas spät dran, aber jetzt geht es los. Android funktioniert grundsätzlich mit zwei Thread-Typen, nämlich UI-Thread und Hintergrund-Thread . Laut Android-Dokumentation -

Greifen Sie nicht von außerhalb des UI-Threads auf das Android UI-Toolkit zu, um dieses Problem zu beheben. Android bietet verschiedene Möglichkeiten, von anderen Threads aus auf den UI-Thread zuzugreifen. Hier ist eine Liste von Methoden, die helfen können:

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

Nun gibt es verschiedene Methoden, um dieses Problem zu lösen.

Ich werde es anhand eines Codebeispiels erklären:

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

Klasse zum Ausführen einer Nachrichtenschleife für einen Thread. Threads sind standardmäßig keine Nachrichtenschleife zugeordnet. Um eine zu erstellen, rufen Sie prepare () in dem Thread auf, der die Schleife ausführen soll, und dann loop (), damit Nachrichten verarbeitet werden, bis die Schleife gestoppt wird.

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

Mit AsyncTask können Sie asynchrone Arbeiten an Ihrer Benutzeroberfläche ausführen. Es führt die Blockierungsvorgänge in einem Arbeitsthread aus und veröffentlicht dann die Ergebnisse im UI-Thread, ohne dass Sie Threads und / oder Handler selbst behandeln müssen.

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

Handler

Mit einem Handler können Sie Nachrichten- und ausführbare Objekte senden und verarbeiten, die der MessageQueue eines Threads zugeordnet sind.

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
quelle
7
Das ist genau das, was ich suchte. Besonders das erste Beispiel mitrunOnUiThread
Navin
5
Danke, 5 Jahre Android-Programmierung und ich wusste nie, dass es Viewauch Methoden gibt post(Runnable)und postDelayed(Runnable, long)! So viele Handler vergebens. :)
Fenix ​​Voltres
Für diejenigen, die durch das Handler-Beispiel verwirrt sind: An welchen Thread wird "neuer Handler (Rückruf)" gebunden? Es ist an den Thread gebunden, der den Handler erstellt hat.
Helin Wang
1
Warum ist das die beste Alternative ?
IgorGanapolsky
Ich benutze doInBackground und möchte eine ArrayList zurückbekommen, aber ich erhalte immer den Fehler: Ich kann keinen Handler innerhalb eines Threads erstellen, der nicht Looper.prepare () aufgerufen hat. Sehen Sie, dies ist meine Frage stackoverflow.com/questions/45562615/…, aber ich kann die Lösung aus dieser Antwort hier nicht erhalten
WeSt
120

Toast.makeText()sollte nur vom Haupt- / UI-Thread aufgerufen werden. Looper.getMainLooper () hilft Ihnen dabei:

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

Ein Vorteil dieser Methode ist, dass Sie sie ohne Aktivität oder Kontext verwenden können.

Ayaz Alifov
quelle
2
Danke, die anderen Antworten haben nicht für mich funktioniert. Ich verwende einen Bibliothekszuckerdatensatz, um die Persistenz zu verwalten. Und darin habe ich keine Aktivität. Aber das funktioniert wunderbar
cabaji99
91

Versuchen Sie dies, wenn Sie runtimeException sehen, weil Looper vor dem Handler nicht vorbereitet wurde.

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

handler.postDelayed(new Runnable() {
  @Override
  public void run() {
  // Run your task here
  }
}, 1000 );
Khulja Sim Sim
quelle
Handler ist eine abstrakte Klasse. Dies wird nicht kompiliert
Stealth Rabbi
2
@ StealthRabbi Import Handler aus dem richtigen Namespace dhandroid.os.Handler
NightFury
Dies ist möglicherweise nicht das Problem. In der aufrufenden Klasse period ist möglicherweise kein Looper vorhanden.
IgorGanapolsky
42

Ich bin auf dasselbe Problem gestoßen, und hier ist, wie ich es behoben habe:

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

Um den UIHandler zu erstellen, müssen Sie Folgendes ausführen:

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

Hoffe das hilft.

ChicoBird
quelle
Ich habe versucht, Ihren Code zu verwenden, aber ich habe verloren und onCreate methodbin mir nicht sicher, wie ich in meiner Situation von oder von AsyncTask anrufen soll. Bitte posten Sie den gesamten Code, um zu erfahren, wie die Dinge funktionieren.
Nick Kahn
2
Sollte diese letzte Zeile nicht lauten uiHandler = new UIHandler(uiThread.getLooper()); ?
Beer Me
36

Grund für einen Fehler:

Worker-Threads sind für Hintergrundaufgaben gedacht und Sie können innerhalb eines Worker-Threads nichts auf der Benutzeroberfläche anzeigen, es sei denn, Sie rufen eine Methode wie runOnUiThread auf . Wenn Sie versuchen, etwas im UI-Thread anzuzeigen, ohne runOnUiThread aufzurufen, wird ein angezeigt java.lang.RuntimeException.

Also, wenn Sie in einem sind activityaber Aufruf Toast.makeText()von Worker - Thread, dies zu tun:

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

Der obige Code stellt sicher, dass Sie die Toast-Nachricht in einer anzeigen, UI threadda Sie sie innerhalb der runOnUiThreadMethode aufrufen . Also nicht mehr java.lang.RuntimeException.

Mein Gott
quelle
23

das ist, was ich tat.

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

Visuelle Komponenten sind für Änderungen von externen Threads "gesperrt". Da der Toast also Dinge auf dem Hauptbildschirm anzeigt, die vom Hauptthread verwaltet werden, müssen Sie diesen Code auf diesem Thread ausführen. Ich hoffe, das hilft:)

Eiran
quelle
Ich habe die gleiche Methode angewendet. Lässt dies jedoch die Möglichkeit von Lecks offen, da die anonyme innere Klasse des Runnable einen impliziten Verweis auf die Aktivität enthält?
Peter G. Williams
1
Das ist ein guter Punkt :) Verwenden Sie einfach getApplicationContext () oder ähnliches, um auf der sicheren Seite zu sein. Obwohl ich nie Probleme mit diesem Code hatte, den ich kenne
Eiran
22

Ich habe diesen Fehler erhalten, bis ich Folgendes getan habe.

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

Und machte dies zu einer Singleton-Klasse:

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
quelle
Wo benutzt du Toaster ? In Ihrem ersten Ausschnitt wird es nicht verwendet ...
IgorGanapolsky
1
Es war eine Convenience-Klasse, die ich gerne benutzte Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(lange weiß ich! Wir haben sie später reduziert), obwohl ich seit einiger Zeit keinen Toast mehr verwendet habe
EpicPandaForce
Was ApplicationHolder.INSTANCEbewertet also?
IgorGanapolsky
Eine statische Variable von CustomApplicationset in CustomApplication.onCreate(), da die Anwendung immer vorhanden ist, während der Prozess vorhanden ist, kann dieser Kontext global verwendet werden
EpicPandaForce
12
 runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(mContext, "Message", Toast.LENGTH_SHORT).show();
            }
        });
Coldfin Lab
quelle
2
Das hat bei mir funktioniert und ich benutze LambdarunOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); });
Black_Zerg
Vielen Dank. Es hat bei mir funktioniert
Amin
11

Wunderbare Kotlin-Lösung:

runOnUiThread {
    // Add your ui thread code here
}
Jungledev
quelle
4
runOnUiThreadist ein Teil der Aktivität dhactivity?.runOnUiThread { ... }
AtomicStrongForce
9

Dies liegt daran, dass Toast.makeText () von einem Arbeitsthread aus aufruft. Es sollte wie folgt vom Haupt-UI-Thread aufgerufen werden

runOnUiThread(new Runnable() {
      public void run() {
        Toast toast = Toast.makeText(mContext, "Something", Toast.LENGTH_SHORT);
      }
 });
Biswajit Karmakar
quelle
8

Die Antwort von ChicoBird hat bei mir funktioniert. Die einzige Änderung, die ich vorgenommen habe, war die Erstellung des UIHandlers, in dem ich etwas tun musste

HandlerThread uiThread = new HandlerThread("UIHandler");

Eclipse weigerte sich, etwas anderes zu akzeptieren. Das macht wohl Sinn.

Auch das uiHandlerist eindeutig eine Klasse global irgendwo definiert. Ich behaupte immer noch nicht zu verstehen, wie Android das macht und was los ist, aber ich bin froh, dass es funktioniert. Jetzt werde ich es studieren und sehen, ob ich verstehen kann, was Android tut und warum man all diese Reifen und Schleifen durchlaufen muss. Danke für die Hilfe ChicoBird.

Brian Reinhold
quelle
6

erster Anruf Looper.prepare()und dann Toast.makeText().show()letzter Anruf Looper.loop()wie folgt :

Looper.prepare() // to be able to make toast
Toast.makeText(context, "not connected", Toast.LENGTH_LONG).show()
Looper.loop()
Hasan A Yousef
quelle
Warum wird diese Antwort unterschätzt?
DkPathak
5

Für Rxjava- und RxAndroid-Benutzer:

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
quelle
Diese Klammern sind unnötig
Borja
4

Ich hatte das gleiche Problem, als meine Rückrufe versuchten, einen Dialog anzuzeigen.

Ich habe es mit dedizierten Methoden in der Aktivität - auf der Ebene der Mitglieder der Aktivitätsinstanz - gelöst, die diese verwendenrunOnUiThread(..)

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
quelle
2
Handler handler2;  
HandlerThread handlerThread=new HandlerThread("second_thread");
handlerThread.start();
handler2=new Handler(handlerThread.getLooper());

Jetzt verwendet handler2 einen anderen Thread, um die Nachrichten zu verarbeiten als der Haupt-Thread.

Tarasantan
quelle
1

Um einen Dialog oder einen Toaster in einem Thread anzuzeigen, verwenden Sie am einfachsten das Activity-Objekt.

Zum Beispiel:

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
quelle
0

Toast, AlertDialogs muss auf einem UI-Thread ausgeführt werden. Sie können Asynctask verwenden , um sie in der Android-Entwicklung ordnungsgemäß zu verwenden. In einigen Fällen müssen wir jedoch die Zeitüberschreitungen anpassen, sodass wir Threads verwenden , aber in Threads können wir Toast nicht verwenden, Alertdialogs wie wir In AsyncTask.So benötigen wir einen separaten Handler für Popup- Dateien .

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

Im obigen Beispiel möchte ich meinen Thread in 3 Sekunden schlafen lassen und nachdem ich eine Toast-Nachricht anzeigen möchte, dafür in Ihrem Mainthread- Implementierungshandler.

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

Ich habe hier Switch Case verwendet, da Sie, wenn Sie unterschiedliche Nachrichten auf dieselbe Weise anzeigen müssen, Switch Case innerhalb der Handler-Klasse verwenden können. Ich hoffe, dies hilft Ihnen dabei

Ashana.Jackol
quelle
0

Dies geschieht normalerweise, wenn etwas im Haupt-Thread von einem Hintergrund-Thread aufgerufen wird. Schauen wir uns zum Beispiel ein Beispiel an.

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


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

Im obigen Beispiel legen wir Text in der Textansicht fest, die sich im Haupt-UI-Thread der Methode doInBackground () befindet, die nur für einen Arbeitsthread ausgeführt wird.

Prashant Paliwal
quelle
0

Ich hatte das gleiche Problem und habe es einfach behoben, indem ich den Toast in die onPostExecute () - Überschreibungsfunktion der Asynctask <> eingefügt habe, und es hat funktioniert.

alibabaei12
quelle
-2

Ich benutze den folgenden Code, um Nachricht von Nicht-Haupt-Thread "Kontext" anzuzeigen,

@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();
    }
}

Verwenden Sie dann wie folgt:

class myClass implements IShowMessage{

  showMessage("your message!");
 @Override
    public Context getContext() {
        return getApplicationContext();
    }
}
Mohamed.Abdo
quelle