2011-01-29 4 views
69

У меня есть рабочий поток, который сидит в фоновом режиме, обрабатывая сообщения. Что-то вроде этого:Как создать поток Looper, а затем отправить ему сообщение немедленно?

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

Из основного потока (поток пользовательского интерфейса, не так уж важно), я хотел бы сделать что-то вроде этого:

Worker worker = new Worker(); 
worker.start(); 
worker.handler.sendMessage(...); 

Беда в том, что это ставит меня для прекрасное состояние гонки: во время чтения worker.handler, нет никакого способа убедиться, что рабочий поток уже назначен этому полю!

Я не могу просто создать Handler из конструктора Worker «s, так как конструктор работает в основном потоке, поэтому 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() происходит до wait(), тогда мы никогда не проснемся!

  2. Пройдя первоначальный Message на конструктор Worker, имея способ run(), разместите его. Специальное решение не будет работать для нескольких сообщений, или если мы не хотим сразу его отправлять, но вскоре.

  3. Занято-ожидание до handler Поле больше не null. Да, в крайнем случае ...

Я хотел бы создать Handler и MessageQueue от имени Worker нити, но это не представляется возможным. Каков самый изящный выход из этого?

+12

Любая конкретная причина вы не используете 'HandlerThread'? – CommonsWare

+2

@CommonsWare: Хм, не знал, что он существует. Никаких перекрестных ссылок в документах. Его метод 'getLooper()' блокируется до тех пор, пока у нас не будет 'Looper', тогда мы можем использовать' new Handler (worker.getLooper()) '* из основного потока * для инициализации' Handler'. Это решило бы проблему, верно? – Thomas

+0

думаю. OTOH, я не использую его много, и поэтому я могу что-то упустить. – CommonsWare

ответ

57

Eventual решение (минус проверка ошибок), благодаря 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; 
} 

Основное различие заключается в том, что она не проверьте, работает ли рабочий поток, но он фактически создал петлитель; и способ сделать это - сохранить петлитель в частном поле. Ницца!

+1

Спасибо за это. Я комментирую только, чтобы указать, что waitUntilReady() _must_ будет вызван после employee.start(). Оглядываясь назад, это звучит довольно очевидно, но мне потребовалось немного, чтобы получить то, что я ошибался, получая исключение с нулевым указателем. – fedepaol

+0

Что-то подобное, как указано выше, выполняется в PowerManagerService в android fmk: 'mInitComplete = false; mHandlerThread = новый HandlerThread ("PowerManagerService") { @Override защищен недействительным onLooperPrepared() { super.onLooperPrepared(); initInThread(); } }; mHandlerThread.start(); synchronized (mHandlerThread) { while (! MInitComplete) { try { mHandlerThread.wait(); } задвижка (InterruptedException е) { // Игнорировать }} } ' (http://grepcode.com) – 1O1

+0

Почему вы не инициализирует обработчик в конструктор HandlerThread? Это также обеспечило бы уникальность создания обработчика. На самом деле, все еще есть вероятность, что waitUntilReady вызывается дважды в одном экземпляре Worker, и это может быть трудно отлаживать. – 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; 
    } 

В принципе, если вы расширяете тему на работника и реализации собственного Looper, то ваш основной класс поток должен расширить рабочий и установить обработчик там.

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