Ich habe einen Arbeitsthread im Hintergrund, der Nachrichten verarbeitet. Etwas wie das:
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();
}
}
Vom Haupt-Thread (UI-Thread, nicht dass es darauf ankommt) möchte ich so etwas machen:
Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);
Das Problem ist, dass ich mich auf eine schöne Rennbedingung vorbereite: Zum Zeitpunkt des worker.handler
Lesens kann nicht sichergestellt werden, dass der Worker-Thread diesem Feld bereits zugewiesen wurde!
Ich kann das nicht einfach Handler
aus dem Worker
Konstruktor des erstellen , da der Konstruktor auf dem Hauptthread ausgeführt wird, sodass sich der Konstruktor Handler
dem falschen Thread zuordnet.
Dies scheint kaum ein ungewöhnliches Szenario zu sein. Ich kann mir mehrere Problemumgehungen einfallen lassen, die alle hässlich sind:
Etwas wie das:
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(); } }
Und aus dem Hauptthema:
Worker worker = new Worker(); worker.start(); worker.wait(); // <- ADDED worker.handler.sendMessage(...);
Aber das ist auch nicht zuverlässig: Wenn das
notifyAll()
vor dem passiertwait()
, werden wir nie geweckt!Übergeben einer Initiale
Message
an denWorker
Konstruktor des Konstruktors, damit dierun()
Methode sie veröffentlicht. Eine Ad-hoc-Lösung funktioniert nicht für mehrere Nachrichten oder wenn wir sie nicht sofort, sondern kurz danach senden möchten.Beschäftigt warten, bis das
handler
Feld nicht mehr istnull
. Ja, ein letzter Ausweg ...
Ich möchte ein Handler
und MessageQueue
im Namen des Worker
Threads erstellen , aber dies scheint nicht möglich zu sein. Was ist der eleganteste Ausweg?
quelle
HandlerThread
?getLooper()
Methode blockiert, bis wir eine habenLooper
, dann können wirnew Handler(worker.getLooper())
aus dem Haupt-Thread die initialisierenHandler
. Das würde das Problem lösen, oder?HandlerThread
gut es in IhrWorker
Muster passt . Zumindest erklären Sie es besser als ich, da es Ihr Problem und Ihre Implementierung einer Lösung war - ich habe nur auf eine Hilfsklasse hingewiesen, um das Problem anzugehen.Antworten:
Eventuelle Lösung (minus Fehlerprüfung) dank CommonsWare:
class Worker extends HandlerThread { // ... public synchronized void waitUntilReady() { d_handler = new Handler(getLooper(), d_messageHandler); } }
Und aus dem Hauptthema:
Worker worker = new Worker(); worker.start(); worker.waitUntilReady(); // <- ADDED worker.handler.sendMessage(...);
Dies funktioniert dank der Semantik,
HandlerThread.getLooper()
die blockiert, bis der Looper initialisiert wurde.Im Übrigen ähnelt dies meiner obigen Lösung Nr. 1, da die
HandlerThread
ungefähr wie folgt implementiert ist (ich muss Open Source lieben):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; }
Der Hauptunterschied besteht darin, dass nicht überprüft wird, ob der Arbeitsthread ausgeführt wird, sondern dass tatsächlich ein Looper erstellt wurde. und der Weg, dies zu tun, besteht darin, den Greifer in einem privaten Feld zu lagern. Nett!
quelle
d_handler
?d_messageHandler
behandelt die Nachrichten, richtig? Aber Sie senden Meesage mitwork.handler
Schauen Sie sich den Quellcode von an
HandlerThread
@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
Wenn Sie Thread in Worker erweitern und Ihren eigenen Looper implementieren, sollte Ihre Haupt-Thread-Klasse Worker erweitern und Ihren Handler dort festlegen.
quelle
Das sind meine Lösungen: 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);
Die WorkerThread-Klasse:
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(); } }
quelle
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(); } } }
quelle