Как создать тему 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
будет ассоциировать себя с неправильной нитью.
Это вряд ли кажется необычным сценарием. Я могу придумать несколько обходных путей, все они безобразны:
Что-то вроде этого:
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
, Да, в крайнем случае...
Я хотел бы создать 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();
}
}
}