主线程中的Looper.loop()死循环为什么不会导致ANR?

Posted by Jack on September 6, 2018

主线程中的Looper.loop()死循环为什么不会导致ANR?

源码的 ActivityThread 类中有这么一段代码:

public final class ActivityThread {
  public static void main(String[] args) {
    ...
 
    Looper.prepareMainLooper(); // 注意此处
 
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
 
    ...
 
    Looper.loop(); // 注意此处
 
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
 
// ------------------ Regular JNI ------------------------
 
private native void nDumpGraphicsInfo(FileDescriptor fd);

}

首先我们看到这个 ActivityThread 并不是一个 Thread, 它并没有继承自 Thread, 写java的都知道, 一个 java 程序的入口便是 main 方法, 很显然, activityThread 中拥有这个 main 方法, 所以可以说这是主线程的入口.

按照我们编写 java 的思维, 一个程序的 main 方法执行完成, 便代表着这个程序运行结束, 那么要使 application 一直得到运行,直到用户退出才结束程序, 那么我们势必得阻塞这个线程, 如果不阻塞, 一个APP 刚启动, main 方法结束,直接退出,, 那还玩个毛线…

那么这个阻塞就是通过 Looper.loop() 来实现的, 我们看看源码:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
 
    ...
 
    final MessageQueue queue = me.mQueue;
    
    ...
   
    for (;;) {
        Message msg = queue.next(); // might block ---注意此处
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
         
        ...
        
        try {
            msg.target.dispatchMessage(msg); // 注意此处
 
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
    }
}

我们看到这个方法在获取到调用这个方法的线程(即主线程)的 looper 后, 再通过 looper获取了 messageQueue , 然后进入了一个死循环,我们看到官方的注释, 在 queue.next() 处, might block (当 messageQueue 为空时), 所以此时主线程就阻塞在这个地方了, 从而导致 main方法不会得到退出而因此避免掉 APP 一启动就GG

那么问题来了, 既然阻塞了主线程,那又是如何响应用户操作和回调 activity 的生命周期的方法的呢?

这里就涉及到 Android 中的 Handler 机制原理 和 IPC 机制, 在此简单概括一下:

首先说一下, 当我们启动了一个 application 时, 此时该 application 进程中并不只有主线程一个线程,还有其他两个 Binder 线程(用来和系统进程进行通信操作,接收系统进程发送的通知),可以用下图介绍:

我们看到,当系统收到来自因用户操作而产生的通知时, 会通过 Binder 方式跨进程的通知我们的 application 进程中的 ApplicationThread , 然后 ApplicationThread 又通过 Handler 机制往主线程的 messageQueue 中插入消息, 从而让主线程的 Message msg = queue.next() 这句代码获得一条 message ,然后通过 msg.target.dispatchMessage(msg) 来处理消息,从而实现了整个 Android 程序能够响应用户交互和回调生命周期方法, 让整个 APP 活了起来.

而至于为什么当主线程处于死循环的 Message msg = queue.next() 这句会阻塞线程的代码的时候不会产生 ANR 异常, 那是因为此时 messageQueue 中并没有消息, 因此主线程处于休眠状态,无需占用 cpu 资源, 而当 messageQueue 中有消息时, 系统会唤醒主线程,来处理这条消息.
那么我们在主线程中耗时为什么会造成 ANR 异常呢? ​
那是因为我们在主线程中进行耗时的操作是属于在这个死循环的执行过程中, 如果我们进行耗时操作, 可能会导致这条消息还未处理完成,后面有接受到了很多条消息的堆积,从而导致了 ANR 异常.