如何创建一个 Looper 线程,然后立即向它发送消息?
Posted
技术标签:
【中文标题】如何创建一个 Looper 线程,然后立即向它发送消息?【英文标题】:How to create a Looper thread, then send it a message immediately? 【发布时间】:2011-06-17 19:34:24 【问题描述】:我有一个位于后台处理消息的工作线程。像这样的:
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
会与错误的线程相关联。
这似乎并不罕见。我可以想出几种变通方法,但都很难看:
类似这样的:
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()
之前,那么我们将永远不会被唤醒!
将初始的Message
传递给Worker
的构造函数,让run()
方法发布它。临时解决方案不适用于多条消息,或者如果我们不想立即发送,但很快就会发送。
忙于等待 handler
字段不再是 null
。是的,最后的手段……
我想代表Worker
线程创建Handler
和MessageQueue
,但这似乎是不可能的。最优雅的方法是什么?
【问题讨论】:
您没有使用HandlerThread
的任何特殊原因?
@CommonsWare:嗯,不知道它的存在。文档中没有交叉引用。它的getLooper()
方法阻塞,直到我们有一个Looper
,然后我们可以使用new Handler(worker.getLooper())
从主线程 来初始化Handler
。这样就可以解决问题了,对吧?
我想是的。 OTOH,我自己用的不多,所以我可能会遗漏一些东西。
@CommonsWare:它解决了这个问题。现在,如果您将其作为答案发布,我将在其旁边放一个大的绿色复选标记;)
实际上,我认为如果您自己回答会更好,解释一下HandlerThread
如何适合您的Worker
模式。至少,你会比我解释得更好,因为这是你的问题和解决方案的实现——我只是指出了一个帮助类来解决这个问题。
【参考方案1】:
最终解决方案(减去错误检查),感谢 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;
关键区别在于它不检查工作线程是否在运行,而是它实际上已经创建了一个looper;这样做的方法是将looper存储在一个私有字段中。不错!
【讨论】:
谢谢。我评论只是为了指出 waitUntilReady() 必须 在 worker.start() 之后调用。回想起来,这听起来很明显,但我花了一点时间才明白我在得到空指针异常时出错的地方。 @Snicolas 你不能,因为处理程序绑定到创建它的线程。在 HandlerThread 的构造函数中初始化它会导致在主线程或任何 HandlerThread 被创建的地方有一个处理程序。如果您想将 Handler 构造函数与 Handler 的 looper 作为参数(在 waituntilready 中使用的那个)一起使用,我很确定它会导致死锁。 好的,这很好。谢谢发帖。因此,为了防止处理程序被滥用,您可以将处理程序方法包装到可以充当外观的 HandlerThread 中,并将每次调用委托给处理程序,在检查处理程序被初始化之后。这将是封装它并防止 waitUntilReady 被遗忘的更好方法。 @Thomasd_handler
的主要用途是什么? d_messageHandler
处理消息,对吗?但是您正在使用work.handler
发送消息
这很有趣,但是如果您提供一些关于 d_handler 和 d_messageHandler 是什么以及如何使用它们的注释会更有用。【参考方案2】:
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();
【讨论】:
【参考方案3】:看看HandlerThread
的源码
@Override
public void run()
mTid = Process.myTid();
Looper.prepare();
synchronized (this)
mLooper = Looper.myLooper();
notifyAll();
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
基本上,如果您在 worker 中扩展 Thread 并实现自己的 Looper,那么您的主线程类应该扩展 worker 并在那里设置您的处理程序。
【讨论】:
【参考方案4】:这是我的解决方案: 主要活动:
//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();
【讨论】:
以上是关于如何创建一个 Looper 线程,然后立即向它发送消息?的主要内容,如果未能解决你的问题,请参考以下文章