バックグラウンドでメッセージを処理するワーカースレッドがあります。このようなもの:
_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();
}
}
_
メインスレッド(UIスレッド、重要ではない)から、私はこのようなことをしたいと思います:
_Worker worker = new Worker();
worker.start();
worker.handler.sendMessage(...);
_
問題は、これにより私が美しい競合状態に設定されることです:_worker.handler
_が読み取られた時点で、ワーカースレッドがこのフィールドに既に割り当てられていることを確認する方法はありません!
コンストラクターはメインスレッドで実行されるため、Handler
のコンストラクターからWorker
を単純に作成することはできません。したがって、Handler
は間違ったスレッドに関連付けられます。
これはめったにないシナリオのようには思えません。私はいくつかの回避策を思い付くことができますが、それらはすべていです:
このようなもの:
_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();
}
}
_
そしてメインスレッドから:
_Worker worker = new Worker();
worker.start();
worker.wait(); // <- ADDED
worker.handler.sendMessage(...);
_
しかし、これも信頼できません:notifyAll()
がwait()
の前に発生する場合、起こされることはありません!
最初のMessage
をWorker
のコンストラクターに渡し、run()
メソッドにポストさせます。アドホックなソリューションは、複数のメッセージに対して機能しないか、すぐに送信したくないがすぐに送信したくない場合に機能します。
handler
フィールドがnull
でなくなるまでビジー待機。うん、最後の手段...
Handler
スレッドに代わってMessageQueue
とWorker
を作成したいのですが、これは不可能のようです。これから最もエレガントな方法は何ですか?
CommonsWareのおかげで、最終的なソリューション(マイナスエラーチェック):
_class Worker extends HandlerThread {
// ...
public synchronized void waitUntilReady() {
d_handler = new Handler(getLooper(), d_messageHandler);
}
}
_
そしてメインスレッドから:
_Worker worker = new Worker();
worker.start();
worker.waitUntilReady(); // <- ADDED
worker.handler.sendMessage(...);
_
これは、ルーパーが初期化されるまでブロックするHandlerThread.getLooper()
のセマンティクスのおかげで機能します。
ちなみに、これは上記の私のソリューション#1に似ています。HandlerThread
がおおよそ次のように実装されているためです(オープンソースが大好きです)。
_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;
}
_
主な違いは、ワーカースレッドが実行されているかどうかをチェックしないが、実際にルーパーを作成したことです。その方法は、ルーパーをプライベートフィールドに保存することです。いいね!
これは私のソリューションです: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);
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();
}
}
HandlerThread
のソースコードを見てください
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
基本的に、workerでThreadを拡張し、独自のLooperを実装する場合、メインスレッドクラスはworkerを拡張し、そこにハンドラを設定する必要があります。
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();
}
}
}