美文网首页
Android 主线程中 Looper.loop() 为什么不

Android 主线程中 Looper.loop() 为什么不

作者: yanlong107 | 来源:发表于2020-04-01 17:42 被阅读0次

    第一次被问到这个问题的时候,就再想,为什么会问这问题呢?
    回想了一遍关于Android Handler,Message, MessageQueue 和 Looper 的相关知识,才明白为什么会有这样的问题。

    这个问题是怎么来的?

    因为Looper.loop() 消息循环是个死循环,会不断的在这里处理MessageQueue消息队列中的消息

     /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
            /*
             * 删除了一些无关代码
             */
            for (;;) {
                // 这里循环从queue中获取Message消息,如果没有消息的话这里会阻塞
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                try {
                    //msg.target 对象是发送Message消息的Handler对象,通过Handler的dispatchMessage进行消息处理
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                 /*
                  * 删除了一些无关代码
                  */
                // 消息处理完后,将Message放入对象池中,这样Message.obtain()获取Message时候可以减少对象的创建
                msg.recycleUnchecked();
            }
        }
    

    Looper.loop() 为什么不会卡死主线程

    1, 这里涉及到Linux pipe/epoll机制 ,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里。此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。

    2,在进入死循环之前创建了新binder线程,在代码ActivityThread.main()中,
    thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程.

    public static void main(String[] args) {
            ....
    
            //创建Looper和MessageQueue对象,用于处理主线程的消息
            Looper.prepareMainLooper();
    
            //创建ActivityThread对象
            ActivityThread thread = new ActivityThread(); 
    
            //建立Binder通道 (创建新线程)
            thread.attach(false);
    
            Looper.loop(); //消息循环运行
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    所以 在 Looper.loop() 中阻塞的时候不会卡死主线程。

    Looper.loop() 死循环会特别消耗CPU资源吗?

    这里同样是涉及到Linux pipe/epoll机制,可参考不卡死主线程的原因。对Linux pipe/epoll机制有兴趣可google相关的介绍

    Looper.loop() 会发生ANR 吗?

    下面是ANR在官方文档的介绍:

    ANR

    如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框。ANR 对话框会为用户提供强行退出应用的选项。

    可以看到ANR的发生是在程序在处理Message消息的时候,用的时间太长,导致 Looper.loop() 无法进入下一个循环处理后续的消息。

    所以 ANR 和 Looper.loop()的阻塞 是两个不同的概念。

    • Looper.loop()阻塞是消息队列为空,在等待新的消息,然后进行处理。
    • ANR 是消息队列不为空的时候,程序在处理某一次的Message时,系统检测耗时太久,提示的ANR。

    END!

    参考:
    https://www.zhihu.com/question/34652589
    https://developer.android.com/topic/performance/vitals/anr

    相关文章

      网友评论

          本文标题:Android 主线程中 Looper.loop() 为什么不

          本文链接:https://www.haomeiwen.com/subject/gxuauhtx.html