主线程的Looper消息循环是如何建立的

核心概览

主线程(UI 线程)的消息循环是由 ActivityThread.main() 方法初始化的。它本质上是一个 无限循环,不断地从消息队列(MessageQueue)中取出消息(Message)并分发给对应的 Handler 进行处理。我们触摸屏幕、更新 UI 等操作,都是封装成消息在这个循环中执行的

建立流程的详细分解

下图清晰地展示了主线程 Looper 消息循环的建立流程,以及之后如何与AMS交互并处理消息:

下面我们来详细解读图中的每一步:

第1步:入口点 - ActivityThread.main()

正如上一个问题所讲,Zygote Fork 出子进程后,会通过反射调用 android.app.ActivityThread 类的 main 方法。这是应用进程的起点

java 复制代码
// ActivityThread.java
public static void main(String[] args) {
    // ... 一些初始化代码 ...

    // 1. 初始化主线程的Looper
    Looper.prepareMainLooper();

    // ... 其他初始化 ...

    // 2. 创建ActivityThread对象,并关联到AMS
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    // ... 其他初始化 ...

    // 3. 启动消息循环 - 这是一个无限循环,不会返回
    Looper.loop();

    // 理论上永远不会执行到这里
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
第2步:准备主Looper - Looper.prepareMainLooper()

这个方法专门用于初始化主线程的 Looper

java 复制代码
// Looper.java
public static void prepareMainLooper() {
    // 1. 调用重载方法,quitAllowed参数为false,表示主线程不允许退出
    prepare(false);
    synchronized (Looper.class) {
        // 2. 确保主Looper只被初始化一次
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        // 3. 设置静态变量sMainLooper
        sMainLooper = myLooper();
    }
}

private static void prepare(boolean quitAllowed) {
    // 4. 每个线程只能有一个Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 5. 创建Looper对象,并存入ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

关键点:

  • ThreadLocal:确保每个线程(这里是主线程)都能存取自己唯一的 Looper 实例
  • new Looper(quitAllowed):在 Looper 的构造函数中,创建了消息循环的核心------MessageQueue
java 复制代码
// Looper.java (构造函数)
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); // 创建消息队列
    mThread = Thread.currentThread();
}
第3步:启动消息循环 - Looper.loop()

这是最神奇的部分,一个"死循环"如何保证应用不卡死?

java 复制代码
// Looper.java
public static void loop() {
    // 1. 获取当前线程的Looper和MessageQueue
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;

    // 2. 无限循环开始
    for (;;) {
        // 3. 从消息队列中取消息,可能会阻塞
        Message msg = queue.next();
        if (msg == null) {
            // 没有消息,Looper可能已退出
            return;
        }

        // 4. 使用Message对应的Handler进行分发和处理
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            // ...
        }

        // 5. 回收消息到消息池,以便复用
        msg.recycleUnchecked();
    }
}

关键点解析:

  1. 阻塞式获取消息queue.next() 是核心。当消息队列为空时,这个方法会 阻塞 (进入休眠状态),释放CPU资源。直到有新的消息加入队列,它才会被唤醒并返回消息。这个阻塞-唤醒机制是由 Linux 的 epoll 机制实现的,非常高效
  2. 消息分发msg.target 就是发送这个消息的 Handler 对象。dispatchMessage 方法最终会调用到我们熟悉的 Handler.handleMessage(Message msg) 方法,从而在正确的线程(主线程)上执行我们的代码
  3. 消息复用:消息被处理后会回收到一个消息池中,这样可以避免频繁创建和销毁对象,减少内存抖动

总结与关联

  1. 建立时机 :消息循环是在 ActivityThread.main() 中,在应用进程创建并 attach 到 AMS 之后 建立的
  2. 核心三部曲
    Looper.prepareMainLooper() :创建唯一的 Looper 和 MessageQueue
    ActivityThread.attach() :与系统服务建立连接,为后续接收系统消息(如启动Activity)做准备
    Looper.loop():启动无限循环,开始处理消息
  3. 为什么不会ANR? :ANR 不是因为 loop 循环本身,而是因为单个消息的处理时间过长,导致后续的消息(例如屏幕触摸事件)得不到及时处理。这个循环本身正是保证应用响应的机制
  4. 与系统交互 :AMS 通过 Binder 线程发送过来的请求(如 scheduleLaunchActivity),都会被 ActivityThread 中的 H(一个 Handler)转换成 Message,然后插入主线程的 MessageQueue 中,最终由主线程的 Looper 取出并执行。这就实现了 从Binder线程到主线程的切换
相关推荐
循环不息优化不止3 小时前
深入理解 Jetpack Compose 生命周期
android
ace望世界4 小时前
Fragment的最佳实践:一个简易版的新闻应用-填坑记录
android
CRMEB定制开发4 小时前
PHP多商户接入阿里云识图找商品
android·阿里云·小程序·php·商城系统·微信商城·crmeb
00后程序员张4 小时前
iOS App 混淆实战,在源码不可用情况下的成品加固与测试流程
android·ios·小程序·https·uni-app·iphone·webview
Jeremy_Lee1235 小时前
MySQL 数据导出及备份方法
android
西西学代码6 小时前
安卓开发---写项目的注意事项
android
come112347 小时前
深入分析JAR和WAR包的区别 (指南七)
android·spring boot·后端
用户097 小时前
停止滥用 Dispatchers.IO:Kotlin 协程调度器的深度陷阱与优化实战
android·面试·kotlin
峥嵘life7 小时前
Android16 adb投屏工具Scrcpy介绍
android·开发语言·python·学习·web安全·adb