来自知乎的问题,一直没搞明白,今天好好梳理一下
1. 问题
app程序入口中为主线程准备好了消息队列
而根据 Looper.loop()
源码可知里面是一个死循环在遍历消息队列取消息
而且并也没看见哪里有相关代码为这个死循环准备了一个新线程去运转,但是主线程却并不会因为 Looper.loop()
中的这个死循环卡死,为什么呢?
举个例子,像Activity的生命周期这些方法这些都是在主线程里执行的吧,那这些生命周期方法是怎么实现在死循环体外能够执行起来的?
2. 需要的知识背景
来自大神 gityuan 的回答 --> 要完全彻底理解这个问题,需要准备以下4方面的知识:
- Process/Thread
- Android Binder IPC
- Handler/Looper/MessageQueue消息机制
- Linux pipe/epoll机制。
3. Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
先说说说进程/线程,
进程:每个app运行时前首先创建一个进程,该进程是由Zygote
fork出来的,用于承载App上运行的各种Activity/Service
等组件。
线程:线程对应用来说非常常见,比如每次 new Thread().start
都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别
有了这么准备,再说说死循环问题:
对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出, 当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
4. 没看见哪里有相关代码为这个死循环准备了一个新线程去运转?
实际上在进入死循环前便创建了 binder 线程,在代码 ActivityThread.main()
中:
public static void main(String[] args) {
....
//创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();
//创建ActivityThread对象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (创建新线程)
//具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件, 该Binder线程通过Handler将Message发送给主线程
thread.attach(false);
Looper.loop(); //消息循环运行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
另外,ActivityThread
实际上并非线程,不像 HandlerThread
类,ActivityThread
并没有真正继承 Thread
类,只是往往运行在主线程,该人以线程的感觉,其实承载 ActivityThread
的主线程就是由 Zygote
fork而创建的进程。
4.1 主线程的死循环一直运行是不是特别消耗CPU资源呢?
其实不然,这里就涉及到Linux pipe/epoll
机制,简单说就是在主线程的 MessageQueue
没有消息时,便阻塞在 loop
的 queue.next()
中的 nativePollOnce()
方法里,此时主线程会释放 CPU
资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe
管道写端写入数据来唤醒主线程工作。
这里采用的 epoll
机制,是一种 IO
多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。
所以说, 主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
5. Activity的生命周期是怎么实现在死循环体外能够执行起来的?
ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信
Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:
在 H.handleMessage(msg)
方法中,根据接收到不同的 msg
,执行相应的生命周期。
主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程
结合图说说Activity生命周期,比如暂停Activity,流程如下:
- 线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
- 线程2通过binder传输到App进程的线程4;
- 线程4通过handler消息机制,将暂停Activity的消息发送给主线程;主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()方法,再经过方法的调用,最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。