Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

本文深入探讨了Android中主线程的运作机制,解释了为何主线程不会因Looper.loop()的死循环而卡死,分析了Activity生命周期方法在死循环外执行的原理,以及主线程如何通过创建新线程处理其他事务。

作者:Gityuan
链接:https://www.zhihu.com/question/34652589/answer/90344494
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

要完全彻底理解这个问题,需要准备以下4方面的知识:

  • Process/Thread,

  • Android Binder IPC,

  • Handler/Looper/MessageQueue消息机制,

  • Linux pipe/epoll机制。

总结一下楼主主要有3个疑惑:
1.Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
2.没看见哪里有相关代码为这个死循环准备了一个新线程去运转?
3.Activity的生命周期这些方法这些都是在主线程里执行的吧,那这些生命周期方法是怎么实现在死循环体外能够执行起来的?--------------------------------------------------------------------------------------------------------------------------------------

针对这些疑惑:
(1) Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。有了这么准备,再说说死循环问题:对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

(2) 没看见哪里有相关代码为这个死循环准备了一个新线程去运转?
事实上,会在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:public static void main(String[] args) {

    //创建Looper和MessageQueue对象,用于处理主线程的消息
    Looper.prepareMainLooper();

    //创建ActivityThread对象
    ActivityThread thread = new ActivityThread(); 

    //建立Binder通道 (创建新线程)
    thread.attach(false);

    Looper.loop(); //消息循环运行
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程,具体过程可查看 startService流程分析,这里不展开说,简单说Binder用于进程间通信,采用C/S架构。关于binder感兴趣的朋友,可查看我回答的另一个知乎问题:为什么Android要采用Binder作为IPC机制? - Gityuan的回答另外,ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类,只是往往运行在主线程,该人以线程的感觉,其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。(3) Activity的生命周期是怎么实现在死循环体外能够执行起来的?ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信。Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。 比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法; 再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 上述过程,我只挑核心逻辑讲,真正该过程远比这复杂。主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程,请看接下来的内容:


最后,从进程与线程间通信的角度,通过一张图加深大家对App运行过程的理解:
在这里插入图片描述

system_server进程是系统进程,java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy(简称ATP),ActivityManagerService(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。

App进程则是我们常说的应用程序,主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程 ApplicationThread(简称AT)和ActivityManagerProxy(简称AMP),除了图中画的线程,其中还有很多线程,比如signal catcher线程等,这里就不一一列举。

Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。

结合图说说Activity生命周期,比如暂停Activity,流程如下:

(1)线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
(2)线程2通过binder传输到App进程的线程4;
(3)线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
(4)主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()方法,再经过方法的调用,最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。

<think> 在分析Looper.loop()为什么不会导致ANR之前,我们需要先理解ANR发生的条件和Android消息机制的基本原理。ANR(Application Not Responding)发生在主线程被阻塞超过一定时间(例如5秒),导致用户操作无法及时响应。而Looper.loop()确实包含一个无限循环,这看起来似乎会阻塞主线程,但实际上它正是Android应用能够响应用户交互的关键。 原因在于:Looper.loop()中的循环并非空转,而是依赖于Linux的epoll机制在消息队列空闲时让线程休眠。当没有消息需要处理时,loop()会在MessageQueue.next()处挂起,此时主线程进入睡眠状态并释放CPU资源,直到下一条消息到达或被中断。这种阻塞是高效的,不会消耗CPU资源。 具体分析如下: 1. **消息队列的阻塞机制** MessageQueue使用epoll机制监控文件描述符。当队列为空时,nativePollOnce()会调用Linux的epoll_wait()使线程休眠。这种休眠不占用CPU,直到: - 新消息加入队列唤醒线程 - 有输入事件(如触摸) - 到达定时消息的执行时间 2. **ANR的触发本质** ANR监测的是**消息处理超时**而非循环本身。系统通过埋点监控关键操作: - 输入事件分发:Service/InputManagerService监控事件响应 - BroadcastReceiver:设置超时监控 - Service生命周期:Service进程会检查执行时间 当主线程正在处理消息时,如果超过阈值未完成,才会触发ANR。 3. **生命周期与消息循环的关系** 所有Activity生命周期方法(如onCreate()、onResume())都是通过消息驱动的。例如启动Activity时: ```mermaid graph LR A[ActivityThread.H] --> B[接收LAUNCH_ACTIVITY消息] B --> C[执行handleLaunchActivity] C --> D[调用onCreate/onStart等] ``` 这些消息被加入主线程消息队列,由Looper顺序分发执行。 4. **对比真实ANR场景** 真正的ANR往往发生在: - 主线程执行耗时操作(如网络请求) - 同步等待子线程结果 - 跨进程通信阻塞 此时消息队列中的后续消息(包括输入事件)无法及时处理。 总结关键点: $$ \text{ANR触发条件} = \begin{cases} \text{主线程有消息正在处理} \\ \text{且处理时间} \Delta t > \text{阈值} \end{cases} $$ 而Looper在无消息时处于休眠状态,不满足ANR条件。这种设计既保证了实时消息处理,又避免了不必要的资源消耗。 --- ### 相关问题 1. MessageQueue.next() 的阻塞机制具体如何实现? 2. Android 系统如何监控主线程消息处理超时? 3. 除了主线程,其他线程可以使用 Looper 吗? 4. Handler 机制如何防止内存泄漏? </think> ### Looper.loop()方法不会导致主线程ANR的原因分析 Looper.loop()方法包含一个无限循环,但不会导致ANR的核心原因在于其特殊的阻塞机制和Android系统的消息处理设计: 1. **高效阻塞机制** MessageQueue.next() 在没有消息时会通过Linux的**epoll机制**进入休眠状态: ```java Message next() { // 当队列为空时,通过nativePollOnce()进入阻塞状态 nativePollOnce(ptr, nextPollTimeoutMillis); // ... } ``` 此时主线程会释放CPU资源,直到新消息到达才被唤醒[^3][^1]。这种阻塞不消耗CPU资源,不同于忙等待的死循环。 2. **事件驱动的唤醒机制** 当有新消息加入队列时(如用户点击、系统事件等),系统会通过`nativeWake()`立即唤醒主线程: ```mermaid graph LR A[新消息/事件] --> B[MessageQueue.enqueueMessage] B --> C[nativeWake] C --> D[唤醒Looper.loop] D --> E[执行消息处理] ``` 3. **ANR的真正触发条件** ANR发生在**消息处理超时**而非消息等待时。系统通过埋点监控关键操作: - 输入事件:5秒未响应 - BroadcastReceiver:10秒未完成 - Service生命周期:20秒未完成 超时检测只发生在**消息正在处理时**,而非等待消息期间[^2][^4]。 4. **生命周期的本质** Activity的生命周期方法(如onCreate()、onResume())本身就是通过消息机制执行的: ```java // ActivityThread示例 public void handleMessage(Message msg) { switch (msg.what) { case LAUNCH_ACTIVITY: handleLaunchActivity((ActivityClientRecord) msg.obj); break; // 其他生命周期消息... } } ``` 生命周期方法作为消息在循环中被处理,而非在循环体外执行[^4]。 5. **与真实ANR场景对比** | **场景** | Looper.loop()阻塞 | 真实ANR原因 | |---|---|----| | CPU占用 | 接近0% | 主线程100%忙 | | 阻塞类型 | 被动等待消息 | 主动执行耗时操作 | | 唤醒条件 | 新消息到达 | 操作完成 | ### 总结 Looper.loop()不会导致ANR的关键在于: 1. 空闲时通过epoll机制高效休眠 2. ANR检测的是消息**处理超时**而非消息等待 3. 所有UI操作(包括生命周期)都依赖此消息循环 4. 真正ANR总是由**同步耗时操作**引起,如网络请求、复杂计算等[^1][^2] --- ### 相关问题 1. MessageQueue.next() 的阻塞机制具体如何实现? 2. Android 系统如何监控主线程消息处理超时? 3. 除了主线程,其他线程可以使用 Looper 吗? 4. Handler 机制如何防止内存泄漏?
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值