Как создать поток 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
, но это не представляется возможным. Каков самый изящный выход из этого?
Ответы
Ответ 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;
}
Ключевое различие заключается в том, что он не проверяет, работает ли рабочий поток, но что он фактически создал петлитель; и способ сделать это - сохранить петлитель в частном поле. Ницца!
Ответ 2
взгляните на исходный код 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, то ваш основной класс потока должен расширять рабочий и устанавливать там свой обработчик.
Ответ 3
Это мои решения:
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();
}
}
Ответ 4
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();
}
}
}