> 文档中心 > 【Android面试】主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

【Android面试】主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

Android面试中,你也许会被问到题目中的问题,这里我们基于以下几点来延伸解读其中原因:

1、什么是ANR?ANR发生的原因是什么?

2、Looper为什么要无限循环?

3、线程的几种状态

4、主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

1、什么是ANR?ANR发生的原因是什么?

ANR即Application Not Responding,顾名思义就是应用程序无响应。

在Android中,一般情况下,四大组件均是工作在主线程中的,Android中的Activity Manager和Window Manager会随时监控应用程序的响应情况,如果因为一些耗时操作(网络请求或者IO操作)造成主线程阻塞一定时间(例如造成5s内不能响应用户事件或者BroadcastReceiver的onReceive方法执行时间超过10s),那么系统就会显示ANR对话框提示用户对应的应用处于无响应状态。

简单总结就是以下两点:

1. 不要让主线程干耗时的工作

2. 不要让其他线程阻塞主线程的执行

2、Looper为什么要无限循环?

Looper中重要的两个方法为prepare()、loop()

prepare()源码

Looper在prepare中通过ThreadLocal保证了每个线程Looper对象的唯一性,即对于每个线程,有唯一的Looper对象和MessageQueue队列

loop()源码

显而易见的,主线程中如果没有looper进行循环,那么主线程一运行完毕就会退出。那么我们还能运行APP吗,显然,这是不可能的,Looper主要就是做消息循环,然后由Handler进行消息分发处理,一旦退出消息循环,那么你的应用也就退出了。

总结:Looper的无限循环必不可少

3、why不会卡死?

从上面的Looper源码分析,我们看到,主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的方法里,那么这个方法里面做了什么?是否不会卡死的秘密在这里呢?

我们看一下messageQueue的next方法

next方法

在MessageQueue.next()方法里,会调用一个native方法:nativePollOnce(long ptr, int timeoutMillis),当主线程没有消息可处理的时候,该方法会阻塞主线程。具体的阻塞和唤醒机制,就是Linux的epoll机制了(Linux 下作为高并发 IO 实现的秘密武器)。关于nativie层,我们就不详细解释了,但是还有一个疑问,我们知道阻塞和唤醒肯定是成对出现的,那应用层的唤醒操作是在哪里调用的呢?

这里回答问题之前,我们先简单想一下,从上面分析知道,looper.loop循环会在messageQueue.next方法中,调用nativePollOnce进行阻塞。总结就是当消息队列中没有可处理的消息时,此时主线程会释放CPU资源进行休眠状态,直到有下个消息到达或者有事情发生时,才会唤醒。我们此时不妨大胆猜想一下,唤醒操作应该是在消息入队的时候发生的。

waike操作

通过源码查看,我们知道,的确是在消息入队的时候唤醒的,但是needWake变量又是在什么时候去赋值为true的呢?(false的话,小编在这里就不说了,应该是在消息出队的时候,阻塞的时候,赋值为false的)

messageQueue的next方法

此时我们仔细去看messagequeue的next方法,居然发现,当空闲消息也为空时,此时会把阻塞变量赋值为true。

4、主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

也许讲到这里,很多人已经知道原因了吧!不过习惯使然,我还是要总结一下。

Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。 Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗

讲到这里,各位看官应该知道为什么上面要引入线程状态的了解了吧。