主线程中的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 一启动就 jj.


那么问题来了, 既然阻塞了主线程,那又是如何响应用户操作和回调 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 异常.

参考博文:

https://www.zhihu.com/question/34652589

https://www.jianshu.com/p/72c44d567640

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Looper.prepare()和Looper.loop()是Android消息循环机制的核心方法。Looper.prepare()用于创建当前线程的消息循环对象,而Looper.loop()则用于启动消息循环,使得消息队列的消息得以被处理。在Android,UI线程就是一个消息循环线程,通过Looper.prepare()和Looper.loop()的配合,可以实现UI线程的消息循环机制,从而保证UI界面的流畅性和响应性。具体来说,Looper.prepare()会创建一个Looper对象,并将其存储在ThreadLocal,而Looper.loop()则会不断地从消息队列取出消息,并将其分发到对应的Handler进行处理。在消息处理完成后,Looper.loop()会继续等待下一个消息的到来,从而实现消息循环的效果。 ### 回答2: 在Java,Looper.prepare()和Looper.loop()是与Android消息循环机制相关的两个方法。 首先,Looper.prepare()方法是用来创建当前线程的消息队列Looper对象。每个线程只能有一个Looper对象,用于处理该线程接收到的消息。在调用Looper.prepare()方法后,会创建一个Looper对象,并将其保存在ThreadLocal,以便该线程其他地方可以直接访问。一般来说,在创建Handler之前,需要先调用Looper.prepare()方法。 其次,Looper.loop()方法是调用当前线程的消息循环机制。一旦调用该方法,程序就会进入一个无限循环的状态,不断地从消息队列取出消息并处理。在处理完一条消息后,继续取出下一条消息进行处理,以此类推。这个无限循环直到Looper对象调用了quit()方法,才会退出循环。 在Android开发,通常在线程会调用Looper.prepare()和Looper.loop()方法来初始化和启动消息循环,以便接收并处理用户交互事件或其他异步事件。然后通过创建Handler对象,将需要处理的消息发送到消息队列,Looper.loop()方法会负责从消息队列轮询消息,并根据消息类型调用相应的Handler处理函数。 总之,Looper.prepare()方法创建当前线程的消息队列,Looper.loop()方法负责启动消息循环并处理队列的消息,这两个方法共同构成了Java利用消息队列实现消息循环机制的基础。 ### 回答3: 在Java,Looper.prepare()和Looper.loop()是Android系统要组件之一——消息循环机制(Message Loop Mechanism)的关键方法。消息循环机制是Android系统用来实现多线程之间通信的重要工具。 首先,Looper.prepare()的作用是在当前线程创建一个消息队列(Message Queue)和Looper对象。消息队列用于存储待处理的消息,而Looper对象则用于管理消息队列。每个线程最多只能有一个Looper对象,保证线程与消息队列一一对应。 其次,Looper.loop()的作用是启动消息循环,开始不断地从消息队列取出消息并依次处理。该方法会一直循环执行,直到调用Looper.quit()方法停止消息循环。 具体来说,当调用Looper.prepare()方法时,会为当前线程创建一个消息队列,并通过ThreadLocal保存Looper对象。然后通过Looper.myLooper()方法可以获取当前线程的Looper对象。接下来,通过Looper.loop()方法启动消息循环,从消息队列获取消息,并传递给Handler进行处理。当消息队列为空时,Looper.loop()方法会进入休眠状态,等待新的消息进入队列。在消息循环期间,调用Looper.quit()方法可以停止消息循环,并释放相关资源。 通过消息循环机制,可以实现多线程间的异步通信。线程通常会创建一个消息循环,用于处理UI事件和与用户交互的消息。在子线程,通过调用Looper.prepare()和Looper.loop()方法,可以为当前线程创建独立的消息队列,从而实现线程间的消息传递和任务调度。 总之,Looper.prepare()和Looper.loop()是Java实现消息循环机制的关键方法,前者用于创建消息队列和Looper对象,后者用于启动消息循环并处理消息。通过合理地使用消息循环机制,可以实现多线程之间的高效通信和任务调度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值