如何创建一个Looper线程并立即向其发送消息?

78

我有一个后台工作线程,用于处理消息。类似于这样:

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

我想从主线程(UI线程,这并不重要)做出如下操作:

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

问题在于,这会导致一个美丽的竞态条件:当读取worker.handler时,无法确定工作线程是否已将其分配给该字段!

我不能简单地从Worker的构造函数创建Handler,因为构造函数在主线程上运行,所以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(...);
    

    但这也不是很可靠:如果在wait()之前发生了notifyAll(),那么我们就永远不会被唤醒!

  2. 将初始的Message传递给Worker的构造函数,并让run()方法发布它。这是一种临时的解决方案,无法用于多个消息,或者如果我们不想立即发送消息,而是稍后再发送。

  3. 忙等待,直到handler字段不再为null。 是的,这是最后的手段...

我想代表Worker线程创建一个HandlerMessageQueue,但似乎不可能实现。有没有更优雅的方法?


12
你没有使用HandlerThread的特殊原因吗? - CommonsWare
2
@CommonsWare:嗯,我不知道它的存在。文档中没有交叉引用。它的getLooper()方法会阻塞直到我们有一个Looper,然后我们可以在主线程中使用new Handler(worker.getLooper())来初始化Handler。这样就解决了问题,对吧? - Thomas
我认为是这样。另一方面,我自己并没有多少使用它,所以可能会有所遗漏。 - CommonsWare
3
它解决了这个问题。现在如果你把它发布为答案,我会在旁边放一个大大的绿色勾勾 ;) - Thomas
9
其实,我认为最好由你自己回答这个问题,解释一下HandlerThread如何适用于你的“工作器”模式。毕竟,这是你的问题和解决方案的实现--我只是指出一个帮助类来解决问题。你会比我更好地解释清楚。 - CommonsWare
4个回答

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()的语法会在looper初始化完成之前阻塞。


顺便说一下,这与我上面提到的第一个解决方案类似,因为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;
}

区别在于它不会检查工作线程是否正在运行,而是实际上创建了一个looper;这样做的方法是将looper存储在私有字段中。很好!


1
谢谢您。我只是想指出,在调用worker.start()之后,必须调用waitUntilReady()。回头看,这听起来非常明显,但我花了一点时间才意识到我的错误,导致出现了空指针异常。 - fedepaol
4
你不能这样做,因为处理程序绑定到创建它的线程。在HandlerThread构造函数中初始化它会导致在主线程或HandlerThread创建的任何地方都有一个处理程序。如果你想使用带有处理程序looper作为参数的处理程序构造函数(waituntilready中使用的那个),我非常确定会导致死锁。 - fedepaol
1
好的,这是一个很好的观点。感谢您的发布。为了保护处理程序不被误用,您可以将处理程序方法包装到HandlerThread中,它可以充当外观并将每个调用委派给处理程序,在检查处理程序已初始化之后。这将是一种更好的封装方式,可以防止忘记waitUntilReady。 - Snicolas
2
@Thomas,d_handler的主要目的是什么?d_messageHandler负责处理消息,对吗?但你正在使用work.handler发送消息。 - woyaru
1
这很有趣,但如果您提供一些关于d_handler和d_messageHandler是什么以及它们如何使用的注释,那将更有用。 - RenniePet
显示剩余5条评论

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

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

基本上,如果您正在扩展工作线程并实现自己的 Looper,则您的主线程类应该扩展 worker 并在那里设置您的 handler。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接