looper原理及不造成ANR原因

940 阅读3分钟

源码的 ActivityThread 类中执行Looper.loop();的main函数,也就是主线程的入口

public final class ActivityThread {

 public static void main(String[] args) {
  		
  		// 为主线程创建looper
        Looper.prepareMainLooper();
		
		// 创建ActivityThread 实例
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        
		// 获取主线程的handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
 		
 		// 开启loop循环
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    	}
 }

按照我们编写 java 的思维, 一个程序的 main 方法执行完成, 便代表着这个程序运行结束, 那么要使 application 一直得到运行,直到用户退出才结束程序, 那么我们势必得让这个线程一直运行下去不能结束, 否则一个APP 刚启动, main 方法结束,直接退出, 那程序也就结束了,那如何让一个线程一直运行呢?这里就用了一个无限循环的阻塞方式

接下来看看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(Handler),将消息发送的对应的handler中执行
                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 一启动就结束

补充: 此处使用了pipe管道机制和epoll

  • pipe机制,在没有消息时阻塞线程并进入休眠释放cpu资源,有消息时唤醒线程
  • Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce() 方法里

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

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

面试之 android Binder 机制 - 掘金 (juejin.cn)

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

在这里插入图片描述
从系统进程system_server看:

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

其中ApplicationThread 是ActivityThread 的内部类,通过如下代码注入,供系统调用

 // 创建ActivityThread 实例
 ActivityThread thread = new ActivityThread();
 // 注入ActivityThread 
 thread.attach(false, startSeq);

 private void attach(boolean system, long startSeq) {
 	final IActivityManager mgr = ActivityManager.getService();
	try {
		 // 注入
         mgr.attachApplication(mAppThread, startSeq);
     } catch (RemoteException ex) {
       throw ex.rethrowFromSystemServer();
     }
 }

而至于为什么当主线程处于死循环的 Message msg = queue.next() 这句会阻塞线程的代码的时候不会产生 ANR 异常, 那是因为此时 messageQueue 中并没有消息,无需处理界面界面更新等操作。 因此主线程处于休眠状态,无需占用 cpu 资源, 而当 messageQueue 中有消息时,,系统会唤醒主线程,来处理这条消息。

那么我们在主线程中耗时为什么会造成 ANR 异常呢?

那是因为我们在主线程中进行耗时的操作是属于在这个死循环的执行过程中, 如果我们进行耗时操作, 可能会导致这条消息还未处理完成,后面有接受到了很多条消息的堆积,从而导致了 ANR 异常.