Как создать тему Looper, а затем немедленно отправить ей сообщение?

У меня есть рабочий поток, который сидит в фоновом режиме, обрабатывая сообщения. Что-то вроде этого:

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

Из основного потока (поток пользовательского интерфейса, не это имеет значение) я хотел бы сделать что-то вроде этого:

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

Проблема в том, что это настраивает меня на прекрасное состояние гонки: в то время worker.handler прочитано, нет никакого способа убедиться, что рабочий поток уже назначен этому полю!

Я не могу просто создать Handler от Workerконструктор, потому что конструктор работает в основном потоке, поэтому Handler будет ассоциировать себя с неправильной нитью.

Это вряд ли кажется необычным сценарием. Я могу придумать несколько обходных путей, все они безобразны:

  1. Что-то вроде этого:

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

    И из основного потока:

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

    Но это тоже ненадежно: если notifyAll() происходит до wait()тогда мы никогда не проснемся!

  2. Прохождение начального Message к Workerконструктор, имеющий run() метод пост это. Специальное решение не подойдет для нескольких сообщений или если мы не хотим отправлять его сразу, но вскоре после этого.

  3. Ожидание, пока handler поле больше не null, Да, в крайнем случае...

Я хотел бы создать Handler а также MessageQueue от имени Worker нить, но это не представляется возможным. Какой самый элегантный выход из этого?

4 ответа

Решение

Возможное решение (без проверки ошибок), благодаря CommonsWare:

class Worker extends HandlerThread {

    // ...

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

}

И из основного потока:

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

Это работает благодаря семантике HandlerThread.getLooper() который блокирует, пока петлитель не был инициализирован.


Кстати, это похоже на мое решение № 1 выше, так как HandlerThread реализован примерно следующим образом (должен любить с открытым исходным кодом):

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

Основное отличие состоит в том, что он не проверяет, запущен ли рабочий поток, но что он фактически создал петлитель; и способ сделать это - хранить петлитель в закрытом поле. Ницца!

Это мои решения: 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);

Класс 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();
    }
}

Взгляните на исходный код HandlerThread

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

По сути, если вы расширяете Thread в работнике и реализуете свой собственный Looper, то ваш основной класс потока должен расширять работника и устанавливать там свой обработчик.

    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();
                    }
            }
    }
Другие вопросы по тегам