Cómo crear un hilo Looper, a continuación, enviar un mensaje de inmediato?
Tengo un hilo del trabajador que se sienta en el fondo, 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 interfaz de usuario, no importa) Me gustaría hacer algo como esto:
- Otto eventbus para android se comporta de forma diferente en la versión de lanzamiento
- Cómo detener / iniciar un servicio que contiene un bucle infinito en la creación
- Es la biblioteca Java.util.concurrent mejor al hacer cualquier tipo de tarea sobre el estándar Android AsyncTask
- Manera más eficiente de pausar el bucle deseado
- ¿Cómo comprobar FileLock sin truncar el archivo?
Worker worker = new Worker(); worker.start(); worker.handler.sendMessage(...);
El problema es que esto me prepara para una condición de carrera hermosa: en el momento en que se lee worker.handler
, no hay manera de estar seguro de que el hilo de trabajo ya ha asignado a este campo!
No puedo simplemente crear el Handler
desde el constructor del Worker
, porque el constructor se ejecuta en el subproceso principal, por lo que el Handler
se asociará con el subproceso incorrecto.
Esto apenas parece un escenario poco común. Puedo llegar a 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 esto no es confiable tampoco: si el
notifyAll()
sucede antes de lawait()
, entonces nunca seremos despertados! -
Pasar un
Message
inicial al constructor delWorker
, haciendo que el métodorun()
publique. Una solución ad-hoc, no funciona para varios mensajes, o si no queremos enviarlo de inmediato, pero poco después. -
Ocupado en espera 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 ser posible. ¿Cuál es la forma más elegante de salir de esto?
- La inicialización de Android TextToSpeech bloquea / congela el subproceso de UI
- Programación con SurfaceView y estrategia de hilos para el desarrollo de juegos
- Cómo utilizar AsyncTask correctamente
- Android Runtime Logcat solo obtiene la instancia actual
- Android SQLite Query, insertar, actualizar, eliminar, siempre tiene que estar en el hilo de fondo?
- ¿Puede TCriticalSection.Acquire ser llamado con seguridad más de una vez por un hilo?
- Multithreading de Android: WaitForGcToComplete después de enviar la aplicación al fondo
- Reproducir varias canciones con MediaPlayer al mismo tiempo: solo una está jugando
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 ha sido inicializado.
Por cierto, esto es similar a mi solución # 1 anterior, ya que el HandlerThread
se implementa aproximadamente como sigue (gotta love open source):
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 está en ejecución, sino que realmente ha creado un looper; Y la manera de hacerlo es almacenar el looper en un campo privado. ¡Bonito!
HandlerThread
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 a worker y establecer su handler 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(); } } }
Esta es mi solución: 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(); } }