77 votes

Comment créer un fil de discussion Looper et lui envoyer immédiatement un message ?

J'ai un thread de travail qui se trouve en arrière-plan et qui traite les messages. Quelque chose comme ceci :

class Worker extends Thread {

    public volatile Handler handler; // actually private, of course

    public void run() {
        Looper.prepare();
        mHandler = new Handler() { // the Handler hooks up to the current Thread
            public boolean handleMessage(Message msg) {
                // ...
            }
        };
        Looper.loop();
    }
}

À partir du fil d'exécution principal (fil d'exécution de l'interface utilisateur, peu importe), j'aimerais faire quelque chose comme ceci :

Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);

L'ennui, c'est que cela m'expose à une magnifique condition de course : au moment où worker.handler est lu, il n'y a aucun moyen de s'assurer que le thread du travailleur a déjà assigné ce champ !

Je ne peux pas simplement créer le Handler de la Worker car le constructeur s'exécute sur le thread principal, de sorte que la fonction Handler s'associera au mauvais fil.

Ce scénario n'est pas rare. J'ai trouvé plusieurs solutions de contournement, toutes très laides :

  1. Voici ce qu'il en est :

    class Worker extends Thread {
    
        public volatile Handler handler; // actually private, of course
    
        public void run() {
            Looper.prepare();
            mHandler = new Handler() { // the Handler hooks up to the current Thread
                public boolean handleMessage(Message msg) {
                    // ...
                }
            };
            notifyAll(); // <- ADDED
            Looper.loop();
        }
    }

    Et dans le fil principal :

    Worker worker = new Worker();
    worker.start();
    worker.wait(); // <- ADDED
    worker.handler.sendMessage(...);

    Mais cela n'est pas fiable non plus : si le notifyAll() se produit avant que le wait() alors on ne nous réveillera jamais !

  2. Le passage d'un Message à la Worker en ayant le constructeur de run() méthode l'affiche. Il s'agit d'une solution ad hoc, qui ne fonctionnera pas pour les messages multiples, ou si nous ne voulons pas l'envoyer tout de suite mais peu après.

  3. Occupé à attendre que le handler n'est plus null . Oui, en dernier recours...

Je voudrais créer un Handler y MessageQueue au nom de la Worker mais cela ne semble pas possible. Quelle est la solution la plus élégante ?

12 votes

Y a-t-il une raison particulière pour laquelle vous n'utilisez pas HandlerThread ?

2 votes

@CommonsWare : Hmm, je ne savais pas que cela existait. Pas de références croisées dans la documentation. Son getLooper() se bloque jusqu'à ce que nous ayons un Looper Nous pouvons alors utiliser new Handler(worker.getLooper()) à partir du fil principal pour initialiser le Handler . Cela résoudrait le problème, n'est-ce pas ?

0 votes

Je pense que oui. D'un autre côté, je ne l'utilise pas beaucoup moi-même, et il se peut que quelque chose m'échappe.

64voto

Thomas Points 63635

Solution finale (moins la vérification des erreurs), grâce à CommonsWare :

class Worker extends HandlerThread {

    // ...

    public synchronized void waitUntilReady() {
        d_handler = new Handler(getLooper(), d_messageHandler);
    }

}

Et dans le fil principal :

Worker worker = new Worker();
worker.start();
worker.waitUntilReady(); // <- ADDED
worker.handler.sendMessage(...);

Cela fonctionne grâce à la sémantique de HandlerThread.getLooper() qui se bloque jusqu'à ce que le boucleur ait été initialisé.


Incidemment, cette solution est similaire à la solution 1 ci-dessus, puisque la fonction HandlerThread est mis en œuvre à peu près comme suit (il faut aimer l'open source) :

public void run() {
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Looper.loop();
}

public Looper getLooper() {
    synchronized (this) {
        while (mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

La principale différence est qu'il ne vérifie pas si le thread du travailleur est en cours d'exécution, mais qu'il a effectivement créé un boucleur ; et la façon de le faire est de stocker le boucleur dans un champ privé. C'est très bien !

1 votes

Merci pour cette initiative. Je commente juste pour signaler que la fonction waitUntilReady() doit soit appelé après worker.start() . Avec le recul, cela semble assez évident, mais il m'a fallu un peu de temps pour comprendre ce que je faisais de travers en obtenant une exception de pointeur nul.

0 votes

Quelque chose de similaire à ce qui précède est fait dans PowerManagerService dans Android fmk : mInitComplete = false; mHandlerThread = new HandlerThread("PowerManagerService") { @Override protected void onLooperPrepared() { super.onLooperPrepared(); initInThread(); } }; mHandlerThread.start(); synchronized (mHandlerThread) { while (!mInitComplete) { try { mHandlerThread.wait(); } catch (InterruptedException e) { // Ignore } } } ( grepcode.com )

0 votes

Pourquoi ne pas initialiser le handler dans le constructeur du HandlerThread ? Cela permettrait également de s'assurer que la création du gestionnaire est unique. En fait, il y a toujours une chance que waitUntilReady soit appelé deux fois sur la même instance de Worker, et cela pourrait être difficile à déboguer.

1voto

beebee Points 272

Jeter un coup d'œil au code source de HandlerThread

@Override
     public void run() {
         mTid = Process.myTid();
         Looper.prepare();
         synchronized (this) {
             mLooper = Looper.myLooper();
             notifyAll();
         }
         Process.setThreadPriority(mPriority);
         onLooperPrepared();
         Looper.loop();
         mTid = -1;
     }

Fondamentalement, si vous étendez Thread dans worker et que vous implémentez votre propre Looper, alors votre classe de thread principale devrait étendre worker et y définir votre handler.

1voto

Voici mes solutions : MainActivity :

//Other Code

 mCountDownLatch = new CountDownLatch(1);
        mainApp = this;
        WorkerThread workerThread = new WorkerThread(mCountDownLatch);
        workerThread.start();
        try {
            mCountDownLatch.await();
            Log.i("MsgToWorkerThread", "Worker Thread is up and running. We can send message to it now...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Toast.makeText(this, "Trial run...", Toast.LENGTH_LONG).show();
        Message msg = workerThread.workerThreadHandler.obtainMessage();
        workerThread.workerThreadHandler.sendMessage(msg);

La classe WorkerThread :

public class WorkerThread extends Thread{

    public Handler workerThreadHandler;
    CountDownLatch mLatch;

    public WorkerThread(CountDownLatch latch){

        mLatch = latch;
    }

    public void run() {
        Looper.prepare();
        workerThreadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {

                Log.i("MsgToWorkerThread", "Message received from UI thread...");
                        MainActivity.getMainApp().runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.getMainApp().getApplicationContext(), "Message received in worker thread from UI thread", Toast.LENGTH_LONG).show();
                                //Log.i("MsgToWorkerThread", "Message received from UI thread...");
                            }
                        });

            }

        };
        Log.i("MsgToWorkerThread", "Worker thread ready...");
        mLatch.countDown();
        Looper.loop();
    }
}

0voto

    class WorkerThread extends Thread {
            private Exchanger<Void> mStartExchanger = new Exchanger<Void>();
            private Handler mHandler;
            public Handler getHandler() {
                    return mHandler;
            }
            @Override
            public void run() {
                    Looper.prepare();
                    mHandler = new Handler();
                    try {
                            mStartExchanger.exchange(null);
                    } catch (InterruptedException e) {
                            e.printStackTrace();
                    }
                    Looper.loop();
            }

            @Override
            public synchronized void start() {
                    super.start();
                    try {
                            mStartExchanger.exchange(null);
                    } catch (InterruptedException e) {
                            e.printStackTrace();
                    }
            }
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X