Як створити нитку Looper, а потім негайно надіслати йому повідомлення?


76

У мене є робоча нитка, яка сидить у фоновому режимі і обробляє повідомлення. Щось на зразок цього:

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конструктору «S, що має run()метод пост він. Спеціальне рішення не працюватиме для кількох повідомлень або якщо ми не хочемо надсилати його відразу, але незабаром після цього.

  3. Зайнятий очікуванням, поки handlerполя більше не буде null. Так, крайній засіб ...

Я хотів би створити Handlerі MessageQueueвід імені Workerпотоку, але, схоже, це неможливо. Який найелегантніший вихід із цього?


12
Якась конкретна причина, яку ви не використовуєте HandlerThread?
CommonsWare

2
@CommonsWare: Хм, не знав, що воно існує. У документах немає перехресних посилань. Його getLooper()метод блокується, поки ми не отримаємо a Looper, тоді ми можемо використовувати new Handler(worker.getLooper()) з основного потоку для ініціалізації Handler. Це вирішило б проблему, так?
Томас

Я думаю так. OTOH, я сам цим не надто користуюся, і тому мені щось може не вистачати.
CommonsWare

3
@CommonsWare: Це вирішило проблему. Тепер, якщо ви опублікуєте це як відповідь, я поставлю біля нього великий зелений галочку;)
Томас

9
Насправді, я думаю, було б краще, якби ви відповіли на це самі, щоб пояснити, наскільки це HandlerThreadвідповідає вашому Workerшаблону. Найменше, ти поясниш це краще, ніж я міг, оскільки це була твоя проблема та твоя реалізація рішення - я щойно вказав допоміжний клас для вирішення проблеми.
CommonsWare

Відповіді:


64

Потенційне рішення (за винятком перевірки помилок), завдяки 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()яких блокує до ініціалізації петлітеля.


До речі, це схоже на моє рішення No1 вище, оскільки 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;
}

Ключова відмінність полягає в тому, що він не перевіряє, чи працює робочий потік, а те, що він насправді створив петлітель; і спосіб зробити це - зберігати петлювач у приватному полі. Приємно!


1
Дякую за це. Я коментую лише, щоб зазначити, що waitUntilReady () потрібно викликати після worker.start (). Озираючись назад, це звучить досить очевидно, але мені знадобилося трохи, щоб зрозуміти, що я помилявся, отримуючи виняток нульового покажчика.
fedepaol

4
@Snicolas ви не можете, оскільки обробник обмежений потоком, де він створений. Ініціалізація його в конструкторі HandlerThread призведе до обробника в основному потоці або там, де створений HandlerThread. Якщо ви хочете використовувати конструктор Handler із петлею Handler як параметром (той, який використовується в waituntilready), я майже впевнений, що це призведе до глухого кута.
fedepaol

1
Гаразд, це хороший момент. Thx для розміщення. Отже, щоб захистити обробник від зловживання, ви можете обернути методи обробника у HandlerThread, який може виконувати функцію фасаду та делегувати кожен виклик обробнику після ініціалізації перевірки обробника. Це був би кращий спосіб інкапсулювати його та запобігти забуттю waitUntilReady.
Сніколас

2
@Thomas, яка головна мета d_handler? d_messageHandler обробляє повідомлення, так? Але ви відправляєте повідомлення за допомогоюwork.handler
woyaru

1
Це дуже цікаво, але було б корисніше, якби ви надали кілька приміток про те, що таке d_handler і d_messageHandler, і як вони використовуються.
RenniePet

1

подивіться на вихідний код 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 в worker і впроваджуєте власний Looper, то ваш основний клас потоку повинен розширити worker і встановити там ваш обробник.


1

Це мої рішення: 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();
    }
}

0
    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();
                    }
            }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.