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资源。