thread example android multithreading handler looper

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:

  1. 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 la wait() , ¡entonces nunca nos despertarán!

  2. Pasando un Message inicial al constructor del Worker , haciendo que el método run() publique. Una solución ad-hoc, no funcionará para múltiples mensajes, o si no queremos enviarla de inmediato, pero poco después.

  3. Ocupado esperando hasta que el campo del handler ya no sea null . 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(); } } }