Tengo un hilo de trabajo que se encuentra en segundo plano, procesando mensajes. Algo como esto:
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();
}
}
Desde el hilo principal (hilo de la interfaz de usuario, no es que importe) me gustaría hacer algo como esto:
Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);
El problema es que esto me prepara para una hermosa condición de carrera: en el momento en que worker.handlerse lee, ¡no hay forma de estar seguro de que el hilo de trabajo ya se haya asignado a este campo!
No puedo simplemente crear el Handlerdesde el Workerconstructor de, porque el constructor se ejecuta en el hilo principal, por lo que Handlerse asociará con el hilo incorrecto.
Esto no parece un escenario poco común. Puedo encontrar varias soluciones, todas feas:
Algo como esto:
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(); } }Y del hilo principal:
Worker worker = new Worker(); worker.start(); worker.wait(); // <- ADDED worker.handler.sendMessage(...);Pero esto tampoco es confiable: si
notifyAll()sucede antes delwait(), ¡nunca nos despertaremos!Pasando una inicial
MessagealWorkerconstructor de, haciendo que elrun()método la publique. Una solución ad-hoc, no funcionará para varios mensajes, o si no queremos enviarla de inmediato, pero poco después.Ocupado esperando hasta que el
handlercampo ya no esténull. Sí, un último recurso ...
Me gustaría crear un Handlery MessageQueueen nombre del Workerhilo, pero esto no parece ser posible. ¿Cuál es la forma más elegante de salir de esto?
fuente

HandlerThread?getLooper()método se bloquea hasta que tenemos unLooper, luego podemos usarnew Handler(worker.getLooper())desde el hilo principal para inicializar elHandler. Eso resolvería el problema, ¿verdad?HandlerThreadencaja en suWorkerpatrón. Al menos, lo explicará mejor de lo que yo podría, ya que era su problema y su implementación de una solución; acabo de señalar una clase de ayuda para abordar el problema.Respuestas:
Solución eventual (menos verificación de errores), gracias a CommonsWare:
class Worker extends HandlerThread { // ... public synchronized void waitUntilReady() { d_handler = new Handler(getLooper(), d_messageHandler); } }Y del hilo principal:
Worker worker = new Worker(); worker.start(); worker.waitUntilReady(); // <- ADDED worker.handler.sendMessage(...);Esto funciona gracias a la semántica de
HandlerThread.getLooper()qué bloques hasta que se inicializa el looper.Por cierto, esto es similar a mi solución n. ° 1 anterior, ya que
HandlerThreadse implementa aproximadamente de la siguiente manera (me encanta el código abierto):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; }La diferencia clave es que no verifica si el hilo de trabajo se está ejecutando, sino que en realidad ha creado un bucleador; y la forma de hacerlo es almacenar el looper en un campo privado. ¡Agradable!
fuente
d_handler?d_messageHandlermaneja los mensajes, ¿verdad? Pero estás enviando mensajes usandowork.handlerecha un vistazo al código fuente de
HandlerThread@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }Básicamente, si está extendiendo Thread en worker e implementando su propio Looper, entonces su clase de thread principal debería extender worker y establecer su controlador allí.
fuente
Estas son mis soluciones: 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);La clase 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(); } }fuente
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(); } } }fuente