2011-01-29 27 views
69

Tôi có một chuỗi công nhân nằm trong nền, xử lý tin nhắn. Một cái gì đó như thế này:Cách tạo chủ đề Looper, sau đó gửi tin nhắn ngay lập tức?

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

Từ các chủ đề chính (UI chủ đề, không phải là vấn đề) Tôi muốn làm điều gì đó như thế này:

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

Vấn đề là điều này đặt tôi lên một điều kiện đua đẹp: tại thời điểm worker.handler được đọc, không có cách nào để đảm bảo rằng chuỗi công nhân đã được gán cho trường này!

Tôi không thể tạo đơn giản Handler từ hàm tạo của Worker, bởi vì hàm tạo chạy trên luồng chính, do đó Handler sẽ tự kết hợp với chuỗi sai.

Điều này hầu như không giống như một trường hợp không phổ biến. Tôi có thể đưa ra nhiều cách giải quyết, tất cả trong số họ xấu xí:

  1. Something như thế này:

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

    Và từ các chủ đề chính:

    Worker worker = new Worker(); 
    worker.start(); 
    worker.wait(); // <- ADDED 
    worker.handler.sendMessage(...); 
    

    Nhưng điều này là không đáng tin cậy hoặc : nếu notifyAll() xảy ra trước wait(), thì chúng tôi sẽ không bao giờ bị đánh thức!

  2. Chuyển số Message ban đầu cho hàm tạo Worker, có phương thức run() đăng nó. Một giải pháp đặc biệt, sẽ không hoạt động đối với nhiều thư hoặc nếu chúng tôi không muốn gửi ngay nhưng ngay sau đó.

  3. Bận chờ đến khi trường handler không còn là null. Yep, một phương sách cuối cùng ...

Tôi muốn tạo ra một HandlerMessageQueue thay mặt cho chủ đề Worker, nhưng điều này dường như không thực hiện được. Cách thanh lịch nhất trong số này là gì?

+12

Bất kỳ lý do cụ thể bạn đang không sử dụng 'HandlerThread'? – CommonsWare

+2

@CommonsWare: Hmm, không biết rằng nó đã tồn tại. Không có tham chiếu chéo trong tài liệu. Phương thức 'getLooper()' của nó cho đến khi chúng ta có một 'Looper', sau đó chúng ta có thể sử dụng' Trình xử lý mới (worker.getLooper()) '* từ chuỗi chính * để khởi tạo' Trình xử lý '. Điều đó sẽ giải quyết vấn đề, đúng không? – Thomas

+0

Tôi nghĩ vậy. OTOH, tôi không sử dụng nó nhiều, và vì vậy tôi có thể thiếu một cái gì đó. – CommonsWare

Trả lời

57

giải pháp cuối cùng (trừ lỗi kiểm tra), nhờ CommonsWare:

class Worker extends HandlerThread { 

    // ... 

    public synchronized void waitUntilReady() { 
     d_handler = new Handler(getLooper(), d_messageHandler); 
    } 

} 

Và từ các chủ đề chính:

Worker worker = new Worker(); 
worker.start(); 
worker.waitUntilReady(); // <- ADDED 
worker.handler.sendMessage(...); 

này hoạt động nhờ vào ngữ nghĩa của HandlerThread.getLooper() mà khối cho đến khi looper có đã được khởi tạo.


Ngẫu nhiên, điều này cũng tương tự như giải pháp của tôi # 1 ở trên, kể từ khi HandlerThread được thực hiện xấp xỉ như sau (gotta yêu mã nguồn mở):

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

Sự khác biệt chính là nó không kiểm tra xem chuỗi công nhân có đang chạy hay không, nhưng nó đã thực sự tạo ra một looper; và cách để làm như vậy là để lưu trữ các looper trong một lĩnh vực tư nhân. Tốt đẹp!

+1

Cảm ơn vì điều này. Tôi đang bình luận chỉ để chỉ ra rằng waitUntilReady() _must_ được gọi sau khi worker.start(). Nhìn lại nó âm thanh khá rõ ràng, nhưng nó đã cho tôi một chút để có được những gì tôi đã nhận được sai trong khi nhận được một ngoại lệ con trỏ null. – fedepaol

+0

Tương tự như trên đang được thực hiện trong PowerManagerService trong android fmk: 'mInitComplete = false; mHandlerThread = new HandlerThread ("PowerManagerService") { @Override void được bảo vệ onLooperPrepared() { super.onLooperPrepared(); initInThread(); } }; mHandlerThread.start(); đồng bộ hóa (mHandlerThread) { trong khi (! MInitComplete) { thử { mHandlerThread.wait(); } catch (InterruptedException e) {// Bỏ qua }} } ' (http://grepcode.com) – 1O1

+0

Tại sao bạn không khởi handler trong constructor của HandlerThread? Nó cũng sẽ đảm bảo việc tạo ra trình xử lý là duy nhất. Trên thực tế, vẫn còn một cơ hội mà waitUntilReady được gọi hai lần trên cùng một cá thể Worker và có thể khó gỡ lỗi. – 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

hãy nhìn vào mã nguồn của HandlerThread

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

Về cơ bản, nếu bạn đang mở rộng chủ đề trong lao động và thực hiện Looper của riêng bạn, sau đó chính lớp chủ đề của bạn nên kéo dài công nhân và thiết lập xử lý của bạn đó.

1

Đây là giải pháp của tôi: 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); 

Các 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(); 
    } 
} 
Các vấn đề liên quan