【问题标题】:Handlers, MessageQueue, Looper, do they all run on the UI thread?Handlers、MessageQueue、Looper,它们都运行在 UI 线程上吗?
【发布时间】:2011-07-08 19:29:24
【问题描述】:

我正在尝试围绕线程进行处理,并且我知道我可以使用Handler 将消息/可运行文件发布到MessageQueue,然后由Looper 接收并发回到Handler处理。

如果我在我的活动中发布到 HandlerActivityHandlerMessageQueueLooper 是否都在 UI 线程上运行?如果没有,有人可以解释一下这一切是如何结合在一起的吗? :)

【问题讨论】:

    标签: android multithreading android-handler android-looper


    【解决方案1】:

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

    长答案:

    一个线程可能有一个Looper,其中包含一个MessageQueue为了使用这个功能,你必须在当前线程上创建一个Looper,通过调用(静态)Looper.prepare(),然后通过调用(也是静态的)Looper.loop() 开始循环。这些是静态的,因为每个线程应该只有一个 Looper

    loop() 的调用通常在一段时间内不会返回,但会不断从MessageQueue 中获取消息(“任务”、“命令”或您喜欢的任何名称)并单独处理它们(例如,通过回调消息中包含的Runnable)。当队列中没有消息时,线程会阻塞,直到有新消息。要停止Looper,您必须对其调用quit()(这可能不会立即停止循环,而是设置一个从循环中定期检查的私有标志,指示它停止)。

    但是,您不能直接将消息添加到队列中。相反,您注册一个MessageQueue.IdleHandler 以等待queueIdle() 回调,您可以在其中决定是否要执行某些操作。依次调用所有处理程序。 (所以“队列”并不是真正的队列,而是定期调用的回调集合。)

    关于上一段的注意事项:我实际上猜到了。我找不到任何关于此的文档,但这是有道理的。

    更新:ahcox' commenthis answer

    因为工作量很大,所以框架提供了Handler 类来简化事情。当您创建Handler 实例时,它(默认情况下)绑定到已附加到当前线程的Looper。 (Handler 知道要附加到什么Looper,因为我们之前调用了prepare(),它可能在ThreadLocal 中存储了对Looper 的引用。)

    使用 Handler,您只需调用 post() 即可“将消息放入线程的消息队列”(可以这么说)。 Handler 将处理所有 IdleHandler 回调的内容,并确保您发布的 Runnable 被执行。 (如果您延迟发布,它也可能会检查时间是否正确。)

    明确一点:真正使循环线程某事的唯一方法是向其循环发布消息。这在您调用 looper 上的 quit() 之前有效。


    关于 android UI 线程: 在某个时间点(可能在创建任何活动等之前)框架设置了一个Looper(包含一个MessageQueue)并启动它。从这一点开始,UI 线程上发生的一切都通过该循环。这包括活动生命周期管理等。您覆盖的所有回调(onCreate()onDestroy()...)至少是从该循环中间接分派的。例如,您可以在异常的堆栈跟踪中看到这一点。 (你可以试试,在onCreate()的某处写int a = 1 / 0;...)


    我希望这是有道理的。抱歉之前不清楚。

    【讨论】:

    • 但是在 Looper.loop() 方法中设置了一个无限循环,从 MessageQueue 中提取消息,为什么这不会锁定 UI 线程上发生的所有其他事情?跨度>
    • 很抱歉我的问题含糊不清,但我还是有点困惑。我得到了关于连接到现有 Looper 的部分。但是现有的 looper 也将包含我正在谈论的这个无限循环。我不明白如何在不锁定同一线程中发生的所有其他事情的情况下在任何线程中运行?
    • 太好了,非常感谢您解决这个问题!关于 UI 线程的最后一部分证实了我的偷偷摸摸的怀疑。
    • 顺便说一句。而不是导致发生异常,您可以只抛出一个。抛出新的 RuntimeException("FooBar");但是,您不能在代码块的中间执行此操作,因为它会使编译器抱怨该块的其余部分将永远无法执行。但是,您可以通过以下方式来欺骗它: if (true) {throw new RuntimeException("Got ya!");} 我喜欢使用我自己,因为您的意图比做有意算术之类的东西更清楚错误。
    • 这是一个非常有帮助的答案。只是为了澄清您的不确定性,MessageQueue 中有一个真正的队列。 Message 包含对 Message 的引用,MessageQueue 使用它来形成侵入式链表队列。您可以在MessageQueue.pullNextLocked()(非公共方法)中看到从其前面抓取的消息。仅当此消息队列为空时,才会调用空闲处理程序。最后的手段是让线程等待下一个延迟的Message 的截止日期,或者等待新的Message 出现。
    【解决方案2】:

    跟进问题的“这一切是如何结合在一起的”部分。正如 user634618 所写,looper 接管了一个线程,主 UI 线程在 应用程序的主Looper

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

    关于所有协作对象都在同一个 UI 线程上的问题, Handler 必须与将发送的 Looper 在同一线程上创建 消息给。 它的构造函数将查找当前的Looper 并将其存储为成员,绑定 HandlerLooper。 它还将直接在其自己的成员中引用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();
    }
    

    【讨论】:

    • 出色的洞察力!谢谢。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      如果我在我的活动中发布到处理程序,活动、处理程序、消息队列和循环器是否都在 UI 线程上运行?如果没有,有人可以解释一下这一切是如何结合在一起的吗? :)

      这取决于你如何创建Handler

      案例一:

      Handler()
      

      默认构造函数将此处理程序与当前线程的Looper 相关联。

      如果你在 UI 线程中这样创建 HandlerHandler 与 UI 线程的 Looper 相关联。 MessageQueue 也与带有 UI 线程的 Looper 相关联。

      案例 2:

      Handler (Looper looper)
      

      使用提供的 Looper 而不是默认的。

      如果我创建一个HandlerThread并将HandlerThread的Looper传递给Handler,Handler和Looper关联的是HandlerThread而不是UI线程。 HandlerMessageQueueLooperHandlerThread 关联。

      用例:您想要执行 Network OR IO 操作。您不能在 UI 线程上执行它,因此 HandlerThread 对您来说很方便。

       HandlerThread handlerThread = new HandlerThread("NetworkOperation");
       handlerThread.start();
       Handler requestHandler = new Handler(handlerThread.getLooper());
      

      如果你想将数据从 HandlerThread 传回 UI Thread,你可以从 UI Thread 创建一个带有 Looper 的 Handler(比如 responseHandler)并调用 sendMessage。 UI 线程 responseHandler 应该覆盖 handleMessage

      有关详细信息,请参阅这些帖子。

      What is the purpose of Looper and how to use it?(用于概念)

      Android: Toast in a thread(例如链接所有这些概念的代码)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-07
        • 1970-01-01
        • 2018-12-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多