文章目录
- 1. 前言
- 2. 分析
- 3. 回归正题
- 3.1 ANR现象
- 3.2 looper阻塞为什么不会造成ANR?
1. 前言
我们都知道真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume
等操作时间过长,会导致掉帧,甚至发生ANR
,looper.loop
本身不会导致应用卡死。
2. 分析
最开始Android
的入口ActivityThread
里面的main
方法,在这个方法中使用Looper.prepareMainLooper();
,而在这个方法中会初始化对应的Looper
,MessageQueue
等对象,所以我们在主线程中才可以默认使用Handler
。
从源码我们知道loop()
的方法,是一个for (;;)
的死循环(类似的:binder
线程也是采用死循环的方法)。而在源码的第一行中写道:
Message msg = queue.next(); // might block
也就是从消息队列中取消息的过程可能会阻塞。不妨看下MessageQueue
的该方法。在该类中使用了比较多的synchronized
字段,且在next
方法中,调用了nativePollOnce(ptr, nextPollTimeoutMillis);
该方法是native
修饰的本地方法。
在主线程的MessageQueue
没有消息时,便阻塞在loop
的queue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU
资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe
管道写端写入数据来唤醒主线程工作。因此,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU
资源。
这里采用的epoll
机制,是一种IO
多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O
,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU
资源。
3. 回归正题
消息循环是死循环,那为什么不会造成ANR
异常呢?
要回答这个问题,这里简单说明一下为什么会有ANR现象
。
3.1 ANR现象
如果主线程处于等待或阻塞状态,操作系统无法调用 onDraw()函数来对屏幕进行重新绘制,这会导致应用冻结,并有可能导致弹出“应用无响应”(ANR) 对话框。
因此通常在Android中有一些关于操作时间的规定,并为了提供更好的用户体验,我们在后台线程上执行此操作。造成ANR
的原因一般有两种:
- 当前的事件没有机会得到处理
- 当前的事件正在处理,但没有及时完成
在ANR(Application Not Responding) 中写道:
在以下四种场景会导致ANR:
InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件。
BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout:前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout :ContentProvider的publish在10s内没进行完。
接着回归正题。
3.2 looper阻塞为什么不会造成ANR?
因为Android
的是由事件驱动的,looper.loop()
不断地接收事件、处理事件,每一个点击触摸或者说Activity
的生命周期都是运行在 Looper.loop()
的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop()
,而不是 Looper.loop()
阻塞它。从直观的逻辑上来说:我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了(否则这么设计还有何意义)。Activity
的生命周期都是依靠主线程的Looper.loop
,当收到不同Message
时则采用相应措施。
从原理角度上来说:主线程Looper
从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从 管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR
异常。