Impossibile creare il gestore all'interno del thread che non ha chiamato Looper.prepare ()


982

Cosa significa la seguente eccezione; come posso ripararlo?

Questo è il codice:

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

Questa è l'eccezione:

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)

8
controlla questa libreria compile 'com.shamanland:xdroid-toaster:0.0.5', non richiede runOnUiThread()o Contextvariabile, tutta la routine è andata! basta invocare Toaster.toast(R.string.my_msg);qui l'esempio: github.com/shamanland/xdroid-toaster-example
Oleksii K.

120
Che stupido messaggio di errore! Potrebbe essere stato semplice come - non è possibile chiamarlo da un thread non UI come fatto quando si toccano le viste da un thread non UI.
Dheeraj Bhaskar,

11
Per coloro che ricevono lo stesso messaggio di eccezione da un codice diverso: ciò che significa il messaggio di eccezione è che si sta chiamando il codice tramite un thread che non ha preparato il Looper. Normalmente significa che non stai chiamando se dal thread dell'interfaccia utente ma dovresti (caso OP) - un thread normale non prepara il Looper, ma lo è sempre il thread dell'interfaccia utente.
Helin Wang,

@OleksiiKropachov l'implementazione della libreria che hai citato è molto simile a fare un runOnUiThread ().
Helin Wang,

sì, ma è un wrapper molto utile
Oleksii K.

Risposte:


696

Lo stai chiamando da un thread di lavoro. È necessario chiamare Toast.makeText()(e la maggior parte delle altre funzioni relative all'interfaccia utente) dall'interno del thread principale. Potresti usare un gestore, per esempio.

Cerca Comunicazione con il thread dell'interfaccia utente nella documentazione. In breve:

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

Altre opzioni:

È possibile utilizzare un AsyncTask , che funziona bene per la maggior parte delle cose in esecuzione in background. Ha hook che puoi chiamare per indicare l'avanzamento e quando è fatto.

È inoltre possibile utilizzare Activity.runOnUiThread () .


che dire del problema originale (non si trattava di AlertDialog)?
Ivan G.

5
Aggiungo solo i miei due centesimi a quello che ha detto Cleggy. Sarebbe preferibile fornire una breve dimostrazione di ciò che intendi (per quanto inventato), poiché un esempio codificato può spesso parlare di volumi da solo.
cdata

5
per una risposta tecnica completa vedere questo prasanta-paul.blogspot.kr/2013/09/…
tony9099

3
In quasi tutti i linguaggi di programmazione AFAIK che supportano la GUI, se aggiorni / cambi / visualizzi / interagisci direttamente con la GUI, dovrebbe essere fatto sul thread principale del programma.
Ahmed,

(and most other functions dealing with the UI)Un esempio di una funzione UI utilizzabile dallo sfondo è android.support.design.widget.Snackbar: la sua funzionalità non è diminuita quando non si chiama dal thread dell'interfaccia utente.
Scruffy,

854

Devi chiamare Toast.makeText(...)dal thread dell'interfaccia utente:

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

Questo viene copiato e incollato da un'altra risposta SO (duplicata) .


13
Bella risposta. Questo mi ha confuso per un po '. Solo per notare, non avevo bisogno dell'attività. prima di eseguireOnUiThread.
Cen92,

448

AGGIORNAMENTO - 2016

La migliore alternativa è quella di utilizzare RxAndroid(attacchi specifici per RxJava) per l' Pin MVPcarica di prendere fo dati.

Inizia tornando Observabledal tuo metodo esistente.

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

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

Usa questo osservabile in questo modo -

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

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

So di essere un po 'in ritardo, ma qui va. Android funziona fondamentalmente su due tipi di thread, ovvero il thread dell'interfaccia utente e il thread in background . Secondo la documentazione Android -

Non accedere al toolkit dell'interfaccia utente Android dall'esterno del thread dell'interfaccia utente per risolvere questo problema, Android offre diversi modi per accedere al thread dell'interfaccia utente da altri thread. Ecco un elenco di metodi che possono aiutare:

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

Ora ci sono vari metodi per risolvere questo problema.

Lo spiegherò per esempio di codice:

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

Classe utilizzata per eseguire un ciclo di messaggi per un thread. Ai thread di default non è associato un loop di messaggi; per crearne uno, chiama prepar () nel thread che deve eseguire il loop, quindi loop () per farlo elaborare i messaggi fino a quando il loop non viene interrotto.

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 ti consente di eseguire lavori asincroni sulla tua interfaccia utente. Esegue le operazioni di blocco in un thread di lavoro e quindi pubblica i risultati sul thread dell'interfaccia utente, senza la necessità di gestire autonomamente thread e / o gestori.

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

Un gestore consente di inviare ed elaborare oggetti Message e Runnable associati a MessageQueue di un thread.

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

7
Questo è esattamente quello che stavo cercando. Soprattutto il primo esempio conrunOnUiThread
Navin

5
Grazie, 5 anni di programmazione Android e non ho mai saputo che Viewha anche metodi post(Runnable)e postDelayed(Runnable, long)! Così tanti gestori invano. :)
Fenix ​​Voltres,

per coloro che sono confusi dall'esempio del gestore: a quale thread si lega il "nuovo gestore (callback)"? È associato al thread che ha creato il gestore.
Helin Wang,

1
Perché questa è la migliore alternativa ?
IgorGanapolsky,

Uso doInBackground e voglio recuperare una ArrayList ma ottengo sempre l'errore: Impossibile creare il gestore all'interno del thread che non ha chiamato Looper.prepare (). Vedi questa è la mia domanda stackoverflow.com/questions/45562615/… ma non riesco a ottenere la soluzione da questa risposta qui
WeSt

120

Toast.makeText()dovrebbe essere chiamato solo dal thread Main / UI. Looper.getMainLooper () ti aiuta a raggiungerlo:

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

Un vantaggio di questo metodo è che puoi usarlo senza Attività o Contesto.


2
Grazie, le altre risposte non hanno funzionato per me. Sto usando un registro degli zuccheri della biblioteca per gestire la persistenza. E al suo interno non ho l'attività. Ma questo funziona meravigliosamente
cabaji99

91

Prova questo, quando vedi runtimeException a causa di Looper non preparato prima del gestore.

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

handler.postDelayed(new Runnable() {
  @Override
  public void run() {
  // Run your task here
  }
}, 1000 );

Handler è una classe astratta. questo non viene compilato
Rabbino

2
Gestore di importazione @StealthRabbi dallo spazio dei nomi corretto, ad es.android.os.Handler
NightFury,

Questo potrebbe non essere il problema. Un looper potrebbe non esistere dalla classe chiamante, punto.
IgorGanapolsky,

42

Ho riscontrato lo stesso problema ed ecco come l'ho risolto:

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

Per creare l'UIHandler, devi eseguire le seguenti operazioni:

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

Spero che sia di aiuto.


ho provato ad usare il tuo codice ma ho perso e non sono sicuro di come chiamare da onCreate methodo da AsyncTask nella mia situazione, per favore, pubblichi l'intero codice solo per sapere come funzionano le cose?
Nick Kahn,

2
Quella riga finale non dovrebbe essere letta uiHandler = new UIHandler(uiThread.getLooper()); ?
Beer Me,

36

Motivo dell'errore:

I thread di lavoro sono pensati per eseguire attività in background e non è possibile mostrare nulla sull'interfaccia utente all'interno di un thread di lavoro a meno che non si chiami metodo come runOnUiThread . Se provi a mostrare qualcosa sul thread dell'interfaccia utente senza chiamare runOnUiThread, ci sarà un java.lang.RuntimeException.

Quindi, se sei in una activityma chiamataToast.makeText() thread dal worker, fai questo:

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

Il codice sopra assicura che tu stia mostrando il messaggio Toast in a UI threadpoiché lo stai chiamando all'interno del runOnUiThreadmetodo. Quindi non di più java.lang.RuntimeException.


23

è quello che ho fatto.

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

I componenti visivi sono "bloccati" alle modifiche da thread esterni. Quindi, poiché il toast mostra elementi nella schermata principale gestita dal thread principale, è necessario eseguire questo codice su quel thread. Spero che aiuti:)


Ho usato questo stesso metodo. Tuttavia, questo lascia aperta la possibilità di perdite, perché la classe interna anonima del Runnable conterrà un riferimento implicito all'attività?
Peter G. Williams,

1
Questo è un buon punto :) basta usare getApplicationContext () o qualcosa del genere, per essere al sicuro. anche se non ho mai avuto problemi con quel codice che conosco
eiran

22

Ho riscontrato questo errore fino a quando non ho fatto quanto segue.

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

E trasformato questo in una classe 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();
                }
            }
        );
    }

}

Dove stai usando Toaster ? Nel tuo primo frammento non viene utilizzato ...
IgorGanapolsky

1
è stata una classe di convenienza che ho usato come Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(molto tempo lo so! Lo abbiamo ridotto in seguito), anche se non uso brindisi da un po 'di tempo
EpicPandaForce

Quindi cosa ApplicationHolder.INSTANCEvaluta?
IgorGanapolsky,

Una variabile statica CustomApplicationimpostata in CustomApplication.onCreate(), considerando che l'applicazione esiste sempre mentre esiste il processo, questo contesto può essere utilizzato a livello globale
EpicPandaForce,

12
 runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(mContext, "Message", Toast.LENGTH_SHORT).show();
            }
        });

2
Questo ha funzionato per me e io uso lambdarunOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); });
Black_Zerg

Grazie. Ha funzionato per me
Amin

11

Soluzione Kotlin meravigliosa:

runOnUiThread {
    // Add your ui thread code here
}

4
runOnUiThreadè una parte di attività cioèactivity?.runOnUiThread { ... }
AtomicStrongForce

9

Questo perché Toast.makeText () sta chiamando da un thread di lavoro. Dovrebbe essere chiamato dal thread dell'interfaccia utente principale in questo modo

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

8

La risposta di ChicoBird ha funzionato per me. L'unica modifica che ho apportato è stata la creazione dell'UIHandler in cui dovevo fare

HandlerThread uiThread = new HandlerThread("UIHandler");

Eclipse ha rifiutato di accettare qualsiasi altra cosa. Ha senso suppongo.

Inoltre uiHandlerè chiaramente una classe globale definita da qualche parte. Continuo a non pretendere di capire come Android stia facendo questo e cosa stia succedendo, ma sono contento che funzioni. Ora procederò a studiarlo e vedere se riesco a capire cosa sta facendo Android e perché uno deve passare attraverso tutti questi cerchi e anelli. Grazie per l'aiuto ChicoBird.


6

prima chiamata Looper.prepare()e quindi Toast.makeText().show()ultima chiamata Looper.loop()come:

Looper.prepare() // to be able to make toast
Toast.makeText(context, "not connected", Toast.LENGTH_LONG).show()
Looper.loop()

Perché questa risposta è sottovalutata?
DkPathak,

5

Per l'utente Rxjava e RxAndroid:

public static void shortToast(String msg) {
    Observable.just(msg)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(message -> {
                Toast.makeText(App.getInstance(), message, Toast.LENGTH_SHORT).show();
            });
}

Queste parentesi non sono necessarie
Borja,

4

Stavo riscontrando lo stesso problema quando i miei callback cercavano di mostrare una finestra di dialogo.

L'ho risolto con metodi dedicati nell'attività - a livello di membro dell'istanza Activity - che usanorunOnUiThread(..)

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

2
Handler handler2;  
HandlerThread handlerThread=new HandlerThread("second_thread");
handlerThread.start();
handler2=new Handler(handlerThread.getLooper());

Ora handler2 utilizzerà un thread diverso per gestire i messaggi rispetto al thread principale.


1

Per visualizzare una finestra di dialogo o un tostapane in un thread, il modo più conciso è utilizzare l'oggetto Activity.

Per esempio:

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()
...

0

Toast, AlertDialogs deve essere eseguito sul thread dell'interfaccia utente, puoi usare Asynctask per usarli correttamente nello sviluppo Android. Ma in alcuni casi dobbiamo personalizzare i timeout, quindi usiamo i thread , ma nei thread non possiamo usare Toast, Alertdialogs come noi in AsyncTask. Quindi abbiamo bisogno di un gestore separato per popup.

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

nell'esempio di cui sopra voglio dormire il mio thread in 3sec e dopo voglio mostrare un messaggio Toast, per quello nel gestore dell'attrezzo mainthread .

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

Ho usato switch case qui, perché se devi mostrare un messaggio diverso allo stesso modo, puoi usare switch case all'interno della classe Handler ... spero che questo ti possa aiutare


0

Questo di solito accade quando qualcosa sul thread principale viene chiamato da qualsiasi thread in background. Vediamo un esempio, per esempio.

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


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

Nell'esempio sopra, stiamo impostando il testo sulla visualizzazione del testo che si trova nel thread dell'interfaccia utente principale dal metodo doInBackground (), che funziona solo su un thread di lavoro.


0

Ho avuto lo stesso problema e l'ho risolto semplicemente inserendo Toast nella funzione di override di OnPostExecute () di Asynctask <> e ha funzionato.


-2

io uso il seguente codice per mostrare il messaggio dal "contesto" di thread non principale,

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

quindi utilizzare come segue:

class myClass implements IShowMessage{

  showMessage("your message!");
 @Override
    public Context getContext() {
        return getApplicationContext();
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.