【问题标题】:How to create a Looper thread, then send it a message immediately?如何创建一个 Looper 线程,然后立即向它发送消息?
【发布时间】:2011-06-17 19:34:24
【问题描述】:

我有一个位于后台处理消息的工作线程。像这样的:

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 时,无法确定工作线程已经分配给该字段!

我不能简单地从Worker的构造函数中创建Handler,因为构造函数在主线程上运行,所以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。是的,最后的手段……

我想代表Worker 线程创建Handler 和MessageQueue,但这似乎是不可能的。最优雅的方法是什么?

【问题讨论】:

  • 您没有使用HandlerThread的任何特殊原因?
  • @CommonsWare:嗯,不知道它的存在。文档中没有交叉引用。它的getLooper() 方法阻塞,直到我们有一个Looper,然后我们可以使用new Handler(worker.getLooper()) 从主线程 来初始化Handler。这样就可以解决问题了,对吧?
  • 我想是的。 OTOH,我自己用的不多,所以我可能会遗漏一些东西。
  • @CommonsWare:它解决了这个问题。现在,如果您将其作为答案发布,我将在其旁边放一个大的绿色复选标记;)
  • 实际上,我认为如果您自己回答会更好,解释一下HandlerThread 如何适合您的Worker 模式。至少,你会比我解释得更好,因为这是你的问题和解决方案的实现——我只是指出了一个帮助类来解决这个问题。

标签: android multithreading handler looper


【解决方案1】:

最终解决方案(减去错误检查),感谢 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;
}

关键区别在于它不检查工作线程是否在运行,而是它实际上已经创建了一个looper;这样做的方法是将looper存储在一个私有字段中。不错!

【讨论】:

  • 谢谢。我评论只是为了指出 waitUntilReady() 必须 在 worker.start() 之后调用。回想起来,这听起来很明显,但我花了一点时间才明白我在得到空指针异常时出错的地方。
  • @Snicolas 你不能,因为处理程序绑定到创建它的线程。在 HandlerThread 的构造函数中初始化它会导致在主线程或任何 HandlerThread 被创建的地方有一个处理程序。如果您想将 Handler 构造函数与 Handler 的 looper 作为参数(在 waituntilready 中使用的那个)一起使用,我很确定它会导致死锁。
  • 好的,这很好。谢谢发帖。因此,为了防止处理程序被滥用,您可以将处理程序方法包装到可以充当外观的 HandlerThread 中,并将每次调用委托给处理程序,在检查处理程序被初始化之后。这将是封装它并防止 waitUntilReady 被遗忘的更好方法。
  • @Thomas d_handler 的主要用途是什么? d_messageHandler 处理消息,对吗?但是您正在使用work.handler 发送消息
  • 这很有趣,但是如果您提供一些关于 d_handler 和 d_messageHandler 是什么以及如何使用它们的注释会更有用。
【解决方案2】:
    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();
                    }
            }
    }

【讨论】:

    【解决方案3】:

    看看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 并在那里设置您的处理程序。

    【讨论】:

      【解决方案4】:

      这是我的解决方案: 主要活动:

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

      【讨论】:

        猜你喜欢
        • 2020-11-08
        • 2011-01-12
        • 2018-09-11
        • 2020-12-28
        • 1970-01-01
        • 2017-05-31
        • 2012-01-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多