2011-01-29 7 views
69

私は、メッセージを処理するバックグラウンドで座っているワーカースレッドを持っています。このような何か:ルーパースレッドを作成してすぐにメッセージを送信する方法は?

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は間違ったスレッドに関連付けられます。

これはまれなシナリオのようです。このような

  1. は何か:

    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()が発生した場合、私たちは目が覚めることはありません!

  2. run()メソッドを持つWorkerのコンストラクタに最初のMessageを渡します。アドホックなソリューションは、複数のメッセージに対しては機能しません。すぐに送信したくない場合はすぐに送信します。

  3. handlerフィールドがもはやnullになるまでビジー状態です。うん、...

最後の私はWorkerスレッドに代わってHandlerMessageQueueを作成したいと思いますが、これは可能ではないようです。この中で最もエレガントな方法は何ですか?

+12

あなたは 'HandlerThread'を使用していない任意の特定の理由は? – CommonsWare

+2

@CommonsWare:うーん、それが存在することを知らなかった。ドキュメント内に相互参照はありません。その 'getLooper()'メソッドは、 'Looper'を持つまでブロックします。そして、メインスレッド*から' Handler'を初期化するために 'new Handler(worker.getLooper())' *を使うことができます。それは問題を解決するでしょう、そうですか? – Thomas

+0

そうだと思います。 OTOH、私は自分自身でそれをあまり使っていないので、何かを見逃しているかもしれません。 – CommonsWare

答えて

57

最終的な解決策(マイナスエラーチェック)、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()どのブロックのセマンティクスのおかげで動作します初期化されました。

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; 
} 

キー違いは、それがないことである:(お奨めは、オープンソースを愛する)を以下のようにHandlerThreadが概ね実現されているので


なお、これは、上記の私の溶液#1と同様ですワーカースレッドが実行されているかどうかを確認しますが、実際にはルーパーを作成しています。そうする方法は、ルーパーをプライベートフィールドに格納することです。ニース!

+1

ありがとうございます。私は、waitUntilReady()_must_がworker.start()の後に呼び出されることを指摘するためにコメントしています。振り返ってみるとかなり分かりやすいですが、NULLポインタの例外を取得している間に私が間違っていたものを取得するのに少し時間がかかりました。 – fedepaol

+0

上記と同じようなことが、android fmkのPowerManagerServiceで行われています: 'mInitComplete = false; mHandlerThread = new HandlerThread( "PowerManagerService"){ @Override プロテクトvoid onLooperPrepared(){ super.onLooperPrepared(); initInThread(); } }; mHandlerThread.start(); synchronized(mHandlerThread){ while(!mInitComplete){ try { mHandlerThread.wait(); }キャッチ(InterruptedExceptionある電子){// 無視 }} } ' (http://grepcode.com) – 1O1

+0

あなたはHandlerThreadのコンストラクタでハンドラを初期化していないのはなぜ?また、ハンドラの作成が一意であることが保証されます。実際には、waitUntilReadyが同じWorkerインスタンスで2回呼び出される可能性があり、それはデバッグするのが難しい場合があります。 – Snicolas

0
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(); 
        } 
      } 
    } 
1

あなたが労働者にスレッドを拡張し、独自のルーパーを実装している場合は、基本的に、そしてあなたのメインスレッドクラスは、労働者を拡張し、あなたのハンドラを設定する必要がありHandlerThread

@Override 
    public void run() { 
     mTid = Process.myTid(); 
     Looper.prepare(); 
     synchronized (this) { 
      mLooper = Looper.myLooper(); 
      notifyAll(); 
     } 
     Process.setThreadPriority(mPriority); 
     onLooperPrepared(); 
     Looper.loop(); 
     mTid = -1; 
    } 

のソースコードを見てみましょうそこ。

1

これは私のソリューションです: 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(); 
    } 
} 
関連する問題