Impossible de créer un gestionnaire à l'intérieur du thread qui n'a pas appelé Looper.prepare ()

984

Que signifie l'exception suivante? comment puis-je le réparer?

Voici le code:

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

C'est l'exception:

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
la source
8
vérifiez cette bibliothèque compile 'com.shamanland:xdroid-toaster:0.0.5', elle n'a pas besoin runOnUiThread()ou Contextvariable, toute routine est partie! juste invoquer Toaster.toast(R.string.my_msg);voici l'exemple: github.com/shamanland/xdroid-toaster-example
Oleksii K.
120
Quel stupide message d'erreur! Cela aurait pu être aussi simple que - ne peut pas appeler cela à partir d'un thread non UI comme cela se fait lorsque les vues sont touchées à partir d'un thread non UI.
Dheeraj Bhaskar
11
Pour ceux qui reçoivent le même message d'exception à partir d'un code différent: ce que signifie le message d'exception, c'est que vous appelez le code via un thread qui n'a pas préparé Looper. Normalement, cela signifie que vous n'appelez pas si à partir d'un thread d'interface utilisateur, mais vous devriez (cas de l'OP) - un thread normal ne prépare pas Looper, mais le thread d'interface utilisateur le fait toujours.
Helin Wang
@OleksiiKropachov l'implémentation de la bibliothèque que vous avez mentionnée est très similaire à faire un runOnUiThread ().
Helin Wang
oui, mais c'est un wrapper très utile
Oleksii K.

Réponses:

697

Vous l'appelez depuis un thread de travail. Vous devez appeler Toast.makeText()(et la plupart des autres fonctions traitant de l'interface utilisateur) à partir du thread principal. Vous pouvez utiliser un gestionnaire, par exemple.

Recherchez Communiquer avec le thread d'interface utilisateur dans la documentation. En un mot:

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

Autres options:

Vous pouvez utiliser une AsyncTask , qui fonctionne bien pour la plupart des choses s'exécutant en arrière-plan. Il a des crochets que vous pouvez appeler pour indiquer la progression et quand c'est fait.

Vous pouvez également utiliser Activity.runOnUiThread () .

EboMike
la source
Qu'en est-il du problème d'origine (il ne s'agissait pas d'AlertDialog)?
Ivan G.
5
J'ajoute juste mes deux cents à ce que Cleggy a dit. Il serait préférable de fournir une brève démonstration de ce que vous voulez dire (même artificiel), car un exemple codé peut souvent en dire long pour lui-même.
cdata
5
pour une réponse technique complète, voir ce prasanta-paul.blogspot.kr/2013/09/…
tony9099
3
Dans presque tous les langages de programmation AFAIK qui prennent en charge l'interface graphique, si vous mettez à jour / modifiez / affichez / interagissez directement avec l'interface graphique, cela doit être fait sur le thread principal du programme.
Ahmed
(and most other functions dealing with the UI)Un exemple de fonction d'interface utilisateur utilisable en arrière-plan est android.support.design.widget.Snackbar- sa fonctionnalité n'est pas diminuée lorsqu'elle n'appelle pas à partir du thread d'interface utilisateur.
Scruffy
855

Vous devez appeler à Toast.makeText(...)partir du thread d'interface utilisateur:

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

Ceci est copié-collé à partir d' une autre réponse SO (en double) .

Jacob Marble
la source
13
Très bonne réponse. Cela m'avait confus pendant un moment. Juste pour noter, je n'avais pas besoin de l'activité. avant runOnUiThread.
Cen92
448

MISE À JOUR - 2016

La meilleure alternative consiste à utiliser RxAndroid(des liaisons spécifiques pour RxJava) pour que l' Pin MVPse charge des données.

Commencez par revenir Observablede votre méthode existante.

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

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

Utilisez cet observable comme ceci -

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

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

Je sais que je suis un peu en retard mais c'est parti. Android fonctionne essentiellement sur deux types de threads, à savoir le thread d'interface utilisateur et le thread d'arrière - plan . Selon la documentation Android -

N'accédez pas au kit d'outils Android UI depuis l'extérieur du thread d'interface utilisateur pour résoudre ce problème, Android propose plusieurs façons d'accéder au thread d'interface utilisateur à partir d'autres threads. Voici une liste de méthodes qui peuvent vous aider:

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

Il existe maintenant différentes méthodes pour résoudre ce problème.

Je vais l'expliquer par exemple de code:

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 utilisée pour exécuter une boucle de message pour un thread. Les threads par défaut n'ont pas de boucle de message qui leur est associée; pour en créer un, appelez prepare () dans le thread qui doit exécuter la boucle, puis loop () pour qu'il traite les messages jusqu'à ce que la boucle soit arrêtée.

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 vous permet d'effectuer un travail asynchrone sur votre interface utilisateur. Il effectue les opérations de blocage dans un thread de travail, puis publie les résultats sur le thread d'interface utilisateur, sans vous obliger à gérer vous-même les threads et / ou les gestionnaires.

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

Gestionnaire

Un gestionnaire vous permet d'envoyer et de traiter des objets Message et Runnable associés à MessageQueue d'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;
    }
});
mjosh
la source
7
C'est exactement ce que je cherchais. Surtout le premier exemple avecrunOnUiThread
Navin
5
Merci, 5 ans de programmation Android et je n'ai jamais su Viewaussi avoir des méthodes post(Runnable)et postDelayed(Runnable, long)! Tant de gestionnaires en vain. :)
Fenix ​​Voltres
pour ceux qui sont confus par l'exemple du gestionnaire: quel est le thread auquel "nouveau gestionnaire (rappel)" est lié? Il est lié au thread qui a créé le gestionnaire.
Helin Wang
1
Pourquoi est-ce la meilleure alternative ?
IgorGanapolsky
J'utilise doInBackground et je veux récupérer un ArrayList mais j'obtiens toujours l'erreur: Impossible de créer un gestionnaire à l'intérieur du thread qui n'a pas appelé Looper.prepare (). Voir c'est ma question stackoverflow.com/questions/45562615/… mais je ne peux pas obtenir la solution de cette réponse ici
WeSt
120

Toast.makeText()ne doit être appelé qu'à partir du thread principal / d'interface utilisateur. Looper.getMainLooper () vous aide à y parvenir:

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

Un avantage de cette méthode est que vous pouvez l'utiliser sans activité ni contexte.

Ayaz Alifov
la source
2
Merci les autres réponses ne fonctionnaient pas pour moi. J'utilise un dossier de sucre de bibliothèque pour gérer la persistance. Et à l'intérieur, je n'ai pas d'activité. Mais cela fonctionne à merveille
cabaji99
91

Essayez ceci, lorsque vous voyez runtimeException en raison de Looper non préparé avant le gestionnaire.

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

handler.postDelayed(new Runnable() {
  @Override
  public void run() {
  // Run your task here
  }
}, 1000 );
Khulja Sim Sim
la source
Le gestionnaire est une classe abstraite. cela ne compile pas
Stealth Rabbi
2
@StealthRabbi import Handler de l'espace de noms correct ieandroid.os.Handler
NightFury
Ce n'est peut-être pas le problème. Un boucleur peut ne pas exister de la classe appelante, point.
IgorGanapolsky
42

J'ai rencontré le même problème, et voici comment je l'ai résolu:

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

Pour créer l'UIHandler, vous devez effectuer les opérations suivantes:

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

J'espère que cela t'aides.

ChicoBird
la source
J'ai essayé d'utiliser votre code mais j'ai perdu et je ne sais pas comment appeler depuis onCreate methodou depuis AsyncTask dans ma situation, veuillez poster le code entier juste pour apprendre comment les choses fonctionnent?
Nick Kahn
2
Cette dernière ligne ne devrait-elle pas être lue uiHandler = new UIHandler(uiThread.getLooper()); ?
Beer Me
36

Raison d'une erreur:

Les threads de travail sont destinés à effectuer des tâches d'arrière-plan et vous ne pouvez rien afficher sur l'interface utilisateur dans un thread de travail, sauf si vous appelez une méthode comme runOnUiThread . Si vous essayez d'afficher quoi que ce soit sur le thread d'interface utilisateur sans appeler runOnUiThread, il y aura unjava.lang.RuntimeException .

Donc, si vous êtes dans un thread de travail, activitymais que vous appelez Toast.makeText(), procédez comme suit:

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

Le code ci-dessus garantit que vous affichez le message Toast dans un UI threadpuisque vous l'appelez à l'intérieur de la runOnUiThreadméthode. Alors pas plus java.lang.RuntimeException.

Mon Dieu
la source
23

C'est ce que j'ai fait.

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

Les composants visuels sont "verrouillés" aux modifications des threads extérieurs. Ainsi, puisque le toast affiche des éléments sur l'écran principal gérés par le thread principal, vous devez exécuter ce code sur ce thread. J'espère que cela pourra aider:)

eiran
la source
J'ai utilisé cette même méthode. Cependant, cela laisse-t-il la possibilité de fuites, car la classe interne anonyme de Runnable contiendra une référence implicite à l'activité?
Peter G. Williams
1
C'est un bon point :) utilisez simplement getApplicationContext () ou quelque chose comme ça, pour être du bon côté. même si je n'ai jamais eu de problème avec ce code que je connaisse
eiran
22

J'obtenais cette erreur jusqu'à ce que je fasse ce qui suit.

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

Et en a fait une 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();
                }
            }
        );
    }

}
EpicPandaForce
la source
Où utilisez-vous Toaster ? Dans votre premier extrait, il n'est pas utilisé ...
IgorGanapolsky
1
c'était une classe de commodité que j'utilisais comme Toaster.INSTANCE.postMessage(ResourceUtils.getString(R.string.blah));(longue je sais! nous l'avons réduite plus tard), bien que je n'utilise pas de toasts depuis un moment
EpicPandaForce
Alors qu'est-ce que cela ApplicationHolder.INSTANCEévalue?
IgorGanapolsky
Une variable statique de CustomApplicationset in CustomApplication.onCreate(), considérant que l'application existe toujours tant que le processus existe, ce contexte peut être utilisé globalement
EpicPandaForce
12
 runOnUiThread(new Runnable() {
            public void run() {
                Toast.makeText(mContext, "Message", Toast.LENGTH_SHORT).show();
            }
        });
Coldfin Lab
la source
2
Cela a fonctionné pour moi et j'utilise lambdarunOnUiThread(() -> { Toast toast = Toast.makeText(getApplicationContext(), "Message", Toast.LENGTH_SHORT); toast.show(); });
Black_Zerg
Merci. Cela a fonctionné pour moi
Amin
11

Magnifique solution Kotlin:

runOnUiThread {
    // Add your ui thread code here
}
jungledev
la source
4
runOnUiThreadfait partie de Activity ieactivity?.runOnUiThread { ... }
AtomicStrongForce
9

Cela est dû au fait que Toast.makeText () appelle à partir d'un thread de travail. Il doit être appelé à partir du thread d'interface utilisateur principal comme celui-ci

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

La réponse de ChicoBird a fonctionné pour moi. Le seul changement que j'ai fait a été dans la création de l'UIHandler où je devais faire

HandlerThread uiThread = new HandlerThread("UIHandler");

Eclipse a refusé d'accepter quoi que ce soit d'autre. C'est logique, je suppose.

De plus, uiHandlerc'est clairement une classe globale définie quelque part. Je ne prétends toujours pas comprendre comment Android fait cela et ce qui se passe, mais je suis heureux que cela fonctionne. Maintenant, je vais l'étudier et voir si je peux comprendre ce que fait Android et pourquoi il faut passer par tous ces cercles et boucles. Merci pour l'aide ChicoBird.

Brian Reinhold
la source
6

premier appel Looper.prepare()puis appelez le Toast.makeText().show()dernier appel Looper.loop()comme:

Looper.prepare() // to be able to make toast
Toast.makeText(context, "not connected", Toast.LENGTH_LONG).show()
Looper.loop()
Hasan A Yousef
la source
Pourquoi cette réponse est-elle sous-estimée?
DkPathak
5

Pour l'utilisateur Rxjava et 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
la source
Ces crochets ne sont pas nécessaires
Borja
4

Je rencontrais le même problème lorsque mes rappels tentaient d'afficher une boîte de dialogue.

Je l'ai résolu avec des méthodes dédiées dans l'activité - au niveau du membre d'instance d' activité - qui utilisentrunOnUiThread(..)

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

Maintenant, handler2 utilisera un Thread différent pour gérer les messages que le Thread principal.

Tarasantan
la source
1

Pour afficher une boîte de dialogue ou un grille-pain dans un fil, la manière la plus concise consiste à utiliser l'objet Activity.

Par exemple:

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
la source
0

Toast, AlertDialogs doit fonctionner sur le thread d'interface utilisateur, vous pouvez utiliser Asynctask pour les utiliser correctement dans le développement Android.Mais dans certains cas, nous devons personnaliser les délais d'attente, nous utilisons donc Threads , mais dans les threads, nous ne pouvons pas utiliser Toast, Alertdialogs comme nous dans AsyncTask. Nous avons donc besoin d'un gestionnaire séparé pour les popups .

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

dans l'exemple ci-dessus, je veux dormir mon thread en 3 secondes et après je veux afficher un message Toast, pour cela dans votre gestionnaire d' implémentation 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);
       }
};

J'ai utilisé le boîtier de commutateur ici, car si vous devez afficher un message différent de la même manière, vous pouvez utiliser le boîtier de commutateur dans la classe Handler ... j'espère que cela vous aidera

Ashana.Jackol
la source
0

Cela se produit généralement lorsqu'un élément du thread principal est appelé à partir de n'importe quel thread d'arrière-plan. Regardons un exemple, par exemple.

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


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

Dans l'exemple ci-dessus, nous définissons du texte sur la vue de texte qui se trouve dans le thread d'interface utilisateur principal de la méthode doInBackground (), qui fonctionne uniquement sur un thread de travail.

Prashant Paliwal
la source
0

J'ai eu le même problème et je l'ai résolu simplement en mettant le Toast dans la fonction de substitution onPostExecute () de Asynctask <> et cela a fonctionné.

alibabaei12
la source
-2

j'utilise le code suivant pour afficher le message du "contexte" du thread non principal,

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

puis utilisez comme suit:

class myClass implements IShowMessage{

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