文章目录

  • 1. 前言
  • 2. 分析
  • 3. 回归正题
  • 3.1 ANR现象
  • 3.2 looper阻塞为什么不会造成ANR?


1. 前言

我们都知道真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANRlooper.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没有消息时,便阻塞在loopqueue.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异常。