android - example - ¿Cómo crear un hilo Looper, y luego enviar un mensaje de inmediato?
runnable android (4)
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();
}
}
Del hilo principal (hilo de 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 se lee worker.handler
, ¡no hay forma de estar seguro de que el hilo de trabajo ya se haya asignado a este campo!
No puedo simplemente crear el Handler
desde el constructor del Worker
, porque el constructor se ejecuta en el hilo principal, por lo que el Handler
se asociará con el hilo equivocado.
Esto apenas 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 desde el hilo principal:
Worker worker = new Worker(); worker.start(); worker.wait(); // <- ADDED worker.handler.sendMessage(...);
Pero tampoco es confiable: si el
notifyAll()
ocurre antes de lawait()
, ¡entonces nunca nos despertarán!Pasando un
Message
inicial al constructor delWorker
, haciendo que el métodorun()
publique. Una solución ad-hoc, no funcionará para múltiples mensajes, o si no queremos enviarla de inmediato, pero poco después.Ocupado esperando hasta que el campo del
handler
ya no seanull
. Sí, un último recurso ...
Me gustaría crear un Handler
y MessageQueue
en nombre del hilo Worker
, pero esto no parece posible. ¿Cuál es la forma más elegante de salir de esto?
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);
The WorkerThread Class:
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();
}
}
Solución eventual (menos comprobación de errores), gracias a CommonsWare:
class Worker extends HandlerThread {
// ...
public synchronized void waitUntilReady() {
d_handler = new Handler(getLooper(), d_messageHandler);
}
}
Y desde el hilo principal:
Worker worker = new Worker();
worker.start();
worker.waitUntilReady(); // <- ADDED
worker.handler.sendMessage(...);
Esto funciona gracias a la semántica de HandlerThread.getLooper()
que bloquea hasta que el looper se haya inicializado.
Dicho sea de paso, esto es similar a mi solución n. ° 1 anterior, ya que el HandlerThread
se implementa más o menos 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 comprueba si el subproceso de trabajo se está ejecutando, sino que realmente ha creado un looper; y la forma de hacerlo es almacenar el looper en un campo privado. ¡Bonito!
echa 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á ampliando Thread in worker e implementando su propio Looper, entonces su clase principal de thread debería extender worker y configurar su controlador allí.
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();
}
}
}