主线程的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线程到主线程的切换
相关推荐
2601_963771374 小时前
Offloading WP-Cron and Securing Ticket Webhooks on Enterprise IT Sites
android
花燃柳卧5 小时前
跨平台路由组件工程源码补充上传
android·flutter·kotlin
apihz5 小时前
随机驾考题目(C 照科一 / 科四 2000+ 题)免费API调用教程
android·java·c语言·开发语言·网络协议·tcp/ip
宸翰6 小时前
uni-app 设置 Android 底部虚拟导航栏背景色
android·前端·uni-app
TechNomad6 小时前
Kotlin类对象与接口详解
android·kotlin
QiLinkOS11 小时前
QiLink OS的失败数据共享平台的运作模式是否适用于所有行业?
android·开发语言·人工智能·算法·重构·开源
杉氧11 小时前
Framework 补完计划 (2):BufferQueue 与 SurfaceFlinger —— 像素的跨进程“接力赛”
android·架构·android jetpack
牢七11 小时前
RCE?复现成功
android
一化十12 小时前
Android16 自定义全局手势 任意界面依次点击屏幕四个角返回Home界面
android
三少爷的鞋13 小时前
Android 面试系列:Kotlin 协程的 delay 到底发生在哪个线程?
android