Android中为什么主线程不会因为Looper.loop的死循环卡死

首先,理解这个问题,我们要先看一下源码ActivityThread.java

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

从上面我们看出来,程序启动起来如果没有创建其他线程,主线程也不是唯一的线程,还有Binder线程。这些线程都可以发送消息到主线程来唤醒它来处理消息

//建立Binder通道 (创建新线程)
thread.attach(false)

以上这行代码是建立Binder线程,这里具体是指的是ApplicaitonThread,用来接收系统服务AMS发送来的事件。此时该Binder线程通过Handler将Message信息发送给主线程(并不是ActivityThread)。

为什么不是ActivityThread呢,这是因为ActivityThread类并不是线程,并没有继承Thread类。这个类给人一种主线程的错觉。我们真正的主线程是由Zygote fork而创建的线程。

 //消息循环运行
Looper.loop();

Looper.java类

for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

当主线程调用了Looper.loop()方法时候,就开始监听消息。此时会调用queue.next(); // might block方法。这个方法里面,会调用一个native方法 : nativePollOnce(long ptr, int timeoutMillis),当线程没有消息处理的时候,此方法会阻塞主线程


说到这里,我们就要说一下,此时的阻塞主线程,并不是我们说的屏幕界面的卡死。这个是两码事

Looper上的阻塞,前提是没有输入事件的,MsgQ是为空的,Looper此时处于空闲状态。线程进入阻塞,释放CPU执行权,等待被唤醒

而UI耗时导致的卡死,这个前提是有输入事件的,MsgQ是不为空的,那么Looper依然会正常轮询。线程也没有阻塞。当事件的执行时间过长的话(5s?),而且此时与其他时间(点击)都没办法处理,那么就是真正的卡死了,然后就ANR了。


上面说了Looper处于空闲状态时,我们在点击一下屏幕,可以发现nativePollOnce()方法继续执行了。并且调用了InputEventReceiver.dispatchInputEvent()方法。而具体的阻塞和唤醒机制,就是epoll机制了。

那么什么是epoll机制呢?

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

猜你喜欢

转载自blog.csdn.net/weixin_34234721/article/details/87048877