处理程序、消息队列、循环处理器,它们是否都在UI线程上运行?

41

我正在尝试理解线程,我知道可以使用Handler将消息/可运行项发布到MessageQueue,然后由Looper拾取并发送回Handler进行处理。

如果我在活动中向Handler发布消息,那么ActivityHandlerMessageQueueLooper是否都在UI线程上运行?如果不是,能否有人解释一下这一切是如何协同工作的呢? :)

4个回答

72

简短回答:它们都在同一个线程上运行。如果从Activity的生命周期回调中实例化,它们都在主UI线程上运行。

详细回答:

一个线程可以拥有一个包含MessageQueueLooper为了使用这个功能,你需要通过调用(静态)Looper.prepare()在当前线程上创建一个Looper,然后通过调用(也是静态的)Looper.loop()来启动循环。这些是静态的,因为每个线程只应该有一个Looper

通常情况下,调用loop()不会立即返回,而是不断地从MessageQueue中取出消息("任务"、"命令"或其他你想称呼它们的东西)并逐个处理它们(例如通过回调消息中包含的Runnable)。当队列中没有消息时,线程会阻塞,直到有新消息。要停止一个Looper,你必须在它上面调用quit()(这可能不会立即停止循环,而是设置一个私有标志,定期从循环中检查,以发出停止信号)。

然而,您不能直接向队列添加消息。相反,您需要注册一个MessageQueue.IdleHandler来等待queueIdle()回调,在其中决定是否要执行某些操作。所有处理程序依次被调用。(因此,“队列”实际上不是队列,而是一组定期调用的回调集合。)
关于上一段的注释:这是我猜测的。我找不到任何关于这方面的文档,但这很有道理。
更新:请参见ahcox的评论他的答案
因为这是很多工作,所以框架提供了Handler类来简化事情。当您创建一个Handler实例时,默认情况下会将其绑定到当前线程已连接的Looper上。(Handler知道要附加到哪个Looper,因为我们之前调用了prepare(),该方法可能在ThreadLocal中存储了对Looper的引用。)
使用一个Handler,你只需要调用post()就可以"将消息放入线程的消息队列中"(这么说吧)。Handler会处理所有IdleHandler回调的事情,并确保您发布的Runnable被执行。(如果您延迟发布,则还可能检查时间是否已经到了。) 需要明确的是:实际上让循环线程做某事的唯一方法是向其循环发送消息。在调用looper的quit()之前,这是有效的。

关于安卓UI线程:在某个时刻(很可能是在任何活动和类似的东西被创建之前),框架已经设置好了一个Looper(包含一个MessageQueue)并启动它。从这一点开始,UI线程上发生的所有事情都是通过该循环进行的。这包括活动生命周期管理等等。您重写的所有回调函数(onCreate()onDestroy()等)至少间接地从该循环分派。例如,在异常的堆栈跟踪中可以看到这一点。(您可以尝试一下,在onCreate()中某处写入int a = 1 / 0;...)


我希望这有意义。之前没有表达清楚,对不起。


1
但是在Looper.loop()方法中设置了一个无限循环,从MessageQueue中获取消息,为什么这不会锁定UI线程上发生的其他所有事情? - rogerkk
1
非常抱歉我的问题有些含糊不清,但我还是有点困惑。我明白连接到现有的looper这一部分。但是这个现有的looper也将包含我所说的这个无限循环。我不明白如何在任何线程中运行它而不会锁定同一线程中发生的其他所有事情? - rogerkk
太好了,非常感谢您的解答!最后关于UI线程的部分确认了我暗中的猜想。 - rogerkk
1
顺便提一下,你可以抛出异常来代替让它发生。 throw new RuntimeException("FooBar"); 不过你不能在代码块中间这样做,因为编译器会抱怨剩下的代码块将永远无法执行。但是,你可以像这样欺骗它: if (true) {throw new RuntimeException("Got ya!");} 我个人喜欢使用这种方法,因为你的意图比例如故意造成算术错误之类的更加清晰。 - Timo
2
这是一个非常有帮助的答案。只是为了澄清你不确定的点,MessageQueue 中有一个真正的队列。Message 包含对 MessageQueue 使用的引用,以形成一个侵入式的链接列表队列。您可以在 MessageQueue.pullNextLocked()(非公共方法)中看到从其前面抓取消息。只有当此消息队列为空时,空闲处理程序才会被调用。最后的选择是线程等待下一个延迟的 Message 截止日期或出现新的 Message - ahcox
显示剩余2条评论

12

关于"如何组合在一起"的问题,用户634618提到,looper会接管一个线程,在应用程序主ui线程的情况下。

  • Looper.loop() 从消息队列中取出Message。每个Message都有一个与之关联的Handler的引用(即目标成员)。
  • 对于从队列中获取的每个消息,在Looper.loop()内部:
    • loop() 调用存储在Message中的Handler作为其目标成员使用的public void Handler.dispatchMessage(Message msg)
    • 如果消息具有Runnable回调成员,则运行该回调。
    • 否则,如果Handler具有共享回调集,则运行它。
    • 否则,将带有Message作为参数调用Handler的handleMessage()。(请注意,如果像AsyncTask一样子类化Handler,则可以覆盖handleMessage()

在您关于所有协作对象都在同一个UI线程上的问题上, 必须在与它将发送消息的Looper相同的线程上创建Handler。 它的构造函数将查找当前Looper并将其存储为成员变量,将Handler绑定到该Looper。 它还将直接引用该Looper的消息队列到自己的成员中。 Handler可用于从任何线程向Looper发送工作,但该消息队列的身份将路由要在Looper的线程上完成的工作。

当我们在另一个线程上运行一些代码并希望发送一个Runnable以在UI线程上执行时,可以这样做:

// h is a Handler that we constructed on the UI thread.
public void run_on_ui_thread(final Handler h, final Runnable r)
{
   // Associate a Message with our Handler and set the Message's
   // callback member to our Runnable:
   final Message message = Message.obtain(h, r);

   // The target is the Handler, so this asks our Handler to put
   // the Message in its message queue, which is the exact same
   // message queue associated with the Looper on the thread on
   // which the Handler was created:
   message.sendToTarget();
}

非常有见地!谢谢。 - Yogesh Umesh Vaity

2
如果我在我的活动中发布到处理程序,那么Activity、Handler、MessageQueue和Looper是否都在UI线程上运行?如果不是,能否有人解释一下这些是如何结合在一起的? :)
取决于您如何创建处理程序。
情况1:
Handler()

默认构造函数将此处理程序与当前线程的 Looper关联起来。
如果您在UI线程中这样创建Handler,则Handler将与UI线程的Looper关联。 MessageQueue也与UI线程的Looper关联。
情况2:
Handler (Looper looper)

使用提供的 Looper 替代默认的 Looper。
如果我创建一个 HandlerThread 并将 HandlerThread 的 Looper 传递给 Handler,那么 Handler、MessageQueue 和 Looper 都与 HandlerThread 关联,而不是 UI 线程。
使用场景:您想要执行网络或 I/O 操作。您不能在 UI 线程上执行它,因此 HandlerThread 对您很有用。
 HandlerThread handlerThread = new HandlerThread("NetworkOperation");
 handlerThread.start();
 Handler requestHandler = new Handler(handlerThread.getLooper());

如果您想要从HandlerThread向UI线程传递数据,可以在UI线程中使用Looper创建另一个Handler(例如responseHandler),并调用sendMessage方法。UI线程的responseHandler应该重写handleMessage方法。
有关更多详细信息,请参阅这些帖子。 什么是Looper及其使用方式?(概念) Android:在线程中使用Toast(通过链接所有这些概念的示例代码)

2

为了理解这个概念,我尝试自己实现这些接口。 为简单起见,只使用必要的接口。 以下是我的测试代码:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class TestLooper {

    public static void main(String[] args) {
        UIThread thread = new UIThread();
        thread.start();

        Handler mHandler = new Handler(thread.looper);
        new WorkThread(mHandler, "out thread").run();
    }
}

class Looper {
    private BlockingQueue<Message> message_list = new LinkedBlockingQueue<Message>();

    public void loop() {

        try {
            while (!Thread.interrupted()) {
                Message m = message_list.take();
                m.exeute();
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void insertMessage(Message msg) {
        message_list.add(msg);
    }

}

class Message {
    String data;
    Handler handler;

    public Message(Handler handler) {
        this.handler = handler;
    }

    public void setData(String data) {
        this.data = data;
    }

    public void exeute() {
        handler.handleMessage(this);
    }
}

class Handler {

    Looper looper;

    public Handler(Looper looper) {
        this.looper = looper;
    }

    public void dispatchMessage(Message msg) {
        System.out.println("Handler dispatchMessage" + Thread.currentThread());
        looper.insertMessage(msg);
    }

    public Message obtainMessage() {
        return new Message(this);
    }

    public void handleMessage(Message m) {
        System.out.println("handleMessage:" + m.data + Thread.currentThread());
    }
}

class WorkThread extends Thread {
    Handler handler;
    String tag;

    public WorkThread(Handler handler, String tag) {
        this.handler = handler;
        this.tag = tag;
    }

    public void run() {
        System.out.println("WorkThread run" + Thread.currentThread());
        Message m = handler.obtainMessage();
        m.setData("message " + tag);
        handler.dispatchMessage(m);
    }
}

class UIThread extends Thread {

    public Looper looper = new Looper();

    public void run() {

            //create handler in ui thread
        Handler mHandler = new Handler(looper);

        new WorkThread(mHandler, "inter thread").run();
        System.out.println("thead run" + Thread.currentThread());
        looper.loop();
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接