为什么主线程没有被 Looper#loop() 方法的死循环卡死

本文深入探讨了Android主线程的运行机制,解释了为何主线程不会因Looper.loop的死循环而卡死,以及它是如何高效地管理和处理消息,同时保持低CPU占用。文章还详细分析了Activity的生命周期流程,揭示了跨进程通信在这一过程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么主线程没有被 Looper.loop 方法的死循环卡死

一、问题

问 1:主线程因为 Looper.loop() 是死循环的,那为什么主线程不会因为死循环卡死?
主线程确实是死循环的,但是在开启死循环之前,执行了 thread.attach(false)这一步就创建了新的线程,建立了 Binder 通道,也即创建了服务端和应用端通信的通道,可以理解创建了 Binder 线程
此时系统进程和应用进程通过 Binder 可以进行跨进程通信,没毛病,即死循环保证的是应用不异常退出,死循环还能处理其他事务是因为在死循环之前开启了新的线程,对事务的处理在新线程中完成
对于事务的处理要反馈给主线程时,是通过 Binder 跨进程调用,然后通过主线程的 Handler 切换到应用进程完成消息的处理

问 2:主线程死循环是不是很消耗 CPU 资源呢?
不不不,这里涉及到 Linux 的 pipe/epoll 机制,当主线程的消息队列没有消息时,会阻塞在 Queue#next() 中的 nativePollOnce() 方法中
此时主线程会释放 CPU 资源进入休眠状态,直到下一个消息到来
这里主线程休眠,就存在唤醒主线程的操作,该操作是通过往 pipe 管道写端写入数据来唤醒主线程
因此,主线程没消息的时候就睡眠了,不会消耗大量 CPU 资源

问 3:主线程的消息从哪来?
其他线程通过 Handler 发送给主线程

二、补问

问 :安卓 APP 启动过程,对于 Activity#onCreate() 等生命周期为什么不会因为 Looper#loop() 里的死循环而无机会执行
答:Activity 的生命周期方法是通过 Binder 跨进程通信实现的,跨进程通信中系统进程不受 Looper#loop() 的死循环的影响(不在同一个线程),系统进程通知应用进程调用 Activity 的生命周期方法,首先通过 ActivityThread 内部的 Handler#handleMessage() 切换线程到主线程,再通过 Handler 完成 Activity 的生命周期方法的调用

三、Activity 生命周期流程

走流程(API 26):

第一步:ActivityThread#main()
	// 准备 Looper
	Looper.prepareMainLooper();

	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	
	// 开始 loop 的死循环
	Looper.loop();
	// 不会执行到这里,到这里就抛异常了
	throw new RuntimeException("Main thread loop unexpectedly exited");
第二步:ActivityThread#attach()
	// 获取 AMS 服务
	final IActivityManager mgr = ActivityManager.getService();
	mgr.attachApplication(mAppThread) // Binder 调用,进入 AMS 所在进程
		AMS#attachApplicationLocked()
			ActivityThread#bindApplication()
				ActivityThread#sendMessage(H.BIND_APPLICATION, data) // 回到应用进程
					handleBindApplication()
						// 1. 获取 Application(此时 Application 为空,反射创建)
						// 2. 设置 appContext.setOuterContext(app),这里的 appContext 对应 Application
						app = data.info.makeApplication(data.restrictedBackupMode, null);
						// 调用 Application#onCreate()
						mInstrumentation.callApplicationOnCreate(app);
			ActivityStackSupervisor#attachApplicationLocked(app)
				realStartActivityLocked()
					app.thread.scheduleLaunchActivity
						ActivityThread$ApplicationThread#scheduleLaunchActivity() // Binder 调用,进入 AMS 所在进程
							ActivityThread#sendMessage(H.LAUNCH_ACTIVITY, r) // 回到应用进程
								handleLaunchActivity()
									// 1. 创建 Activity,再次调用 LoadApk#makeApplication() 获取 Application(上面已经创建了,这里直接返回)
									// 2. 通过 Instrumentation#callActivityOnCreate(),最终调用 Activity#onCreate()
									// 3. appContext.setOuterContext(activity),这里的 appContext 对应 Activity
									Activity a = performLaunchActivity(r, customIntent);
									handleResumeActivity()
										// ActivityClientRecord 通过 token 获取对应 Activity,调用 Activity#performResume(),然后通过 Instrumentation#callActivityOnResume() 调用 Activity#onResume()
										performResumeActivity()
										// 给 Window 赋值(Window 在哪被创建的?在上面的 Activity#attach() 方法中,mWindow 被赋值为 PhoneWindow)
										r.window = r.activity.getWindow();
										View decor = r.window.getDecorView();
										// 设置 Activity 可见
										decor.setVisibility(View.INVISIBLE);
										// 开启 View 的绘制流程
										// 其中完成了 ViewRootImpl 创建(当前在主线程)
										// ViewRootImpl#checkThread() 方法用来检测更新 UI 是否为主线程,子线程更新 UI 会抛异常
										// 但在 ViewRootImpl 创建之前,在子线程中可以更新 UI
										wm.addView(decor, l);
<think>嗯,用户问的是非主线程是不是都没有Looper,即使new Thread之后过了很久。我需要先回想一下之前的知识点。之前已经解释过new Thread默认没有Looper,用户可能现在想确认是否所有非主线程都一直不会Looper,还是说可能在之后被创建。 首先,用户可能误解Looper的存在是否和时间有关,比如线程运行一段时间后自动生成Looper,但其实Looper的创建是显式的,必须手动调用prepare和loop。所以不管线程运行多久,只要没手动创建,就不会Looper。这点需要明确。 然后,用户可能在实际开发中遇到了在子线程使用Handler的问题,或者在阅读代码时看到别人在子线程初始化了Looper,但不确定是否所有情况都需要这么做。需要强调Looper的显式初始化步骤,并举例说明即使过了一段时间,如果不调用prepare,仍然不会Looper。 另外,可能用户对HandlerThread的使用不太熟悉,或者不知道有这种封装好的类,所以需要提到HandlerThread的作用,作为替代方案,方便开发者使用。 还要注意用户可能担心线程生命周期的问题,比如Looper是否会因为线程长时间运行而自动创建。需要解释Looper的工作原理,loop方法会阻塞线程,保持活跃,直到退出。而普通线程如果执行完任务就会结束,没有Looper的话自然不会有消息循环。 最后,确保回答结构清晰,分点说明,并给出示例代码,帮助用户理解。同时要确认回答覆盖了用户可能的疑问点,比如时间因素、显式初始化、替代方案等。</think>在Android中,**非主线程默认没有Looper**,无论线程运行多久,除非**显式调用`Looper.prepare()`**进行初始化。以下是具体分析: --- ### 1. **Looper的初始化逻辑** - **显式创建**:Looper不会随线程运行时间自动生成。必须手动调用以下两个方法: ```java Looper.prepare(); // 创建Looper和关联的MessageQueue Looper.loop(); // 启动消息循环(线程会阻塞在此处,等待处理消息) ``` - **未初始化的表现**:即使线程运行了很长时间,若未调用`Looper.prepare()`,该线程仍**没有消息队列**,直接创建`Handler`会崩溃。 --- ### 2. **非主线程Looper状态验证** - **示例代码**: ```java new Thread(() -> { // 延迟一段时间后测试 SystemClock.sleep(5000); boolean hasLooper = Looper.myLooper() != null; // 返回false Log.d("Test", "线程是否有Looper?" + hasLooper); }).start(); ``` - **结果**:即使线程存活了5秒,`hasLooper`仍为`false`。 --- ### 3. **特殊场景:手动初始化后的Looper** - 如果主动调用`Looper.prepare()`,则线程会拥有Looper: ```java new Thread(() -> { Looper.prepare(); // 初始化Looper boolean hasLooper = Looper.myLooper() != null; // 返回true Looper.loop(); // 必须调用loop()才能处理消息 }).start(); ``` - **关键特性**:调用`Looper.loop()`后,线程会**阻塞在消息循环**中,直到调用`Looper.quit()`。 --- ### 4. **Looper与线程生命周期的关系** - **普通线程**:执行完`Runnable`任务后,线程会终止。 - **带Looper的线程**:调用`Looper.loop()`后,线程会**长期存活**(除非主动退出消息循环)。 - **常见用途**:适用于需要**持续处理异步消息**的场景(如后台任务调度)。 --- ### 5. **替代方案:HandlerThread** - Android提供`HandlerThread`类,其内部已封装了Looper初始化逻辑: ```java HandlerThread workerThread = new HandlerThread("WorkerThread"); workerThread.start(); // 内部自动调用Looper.prepare()loop() Handler handler = new Handler(workerThread.getLooper()); // 可直接使用 ``` - **优势**:避免手动管理Looper,简化线程通信。 --- ### 结论 - **非主线程默认无Looper**,与线程运行时间无关。 - 只有**显式调用`Looper.prepare()`的线程**才会绑定Looper。 - 需要长期处理消息的线程,建议使用`HandlerThread`或手动管理Looper生命周期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值