主线程的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线程到主线程的切换
相关推荐
不吃凉粉4 小时前
Android Studio USB串口通信
android·ide·android studio
zhangphil4 小时前
android studio设置大内存,提升编译速度
android·android studio
编程乐学6 小时前
安卓非原创--基于Android Studio 实现的天气预报App
android·ide·android studio·课程设计·大作业·天气预报·安卓大作业
大熊的瓜地7 小时前
Android automotive 框架
android·android car
私人珍藏库8 小时前
[Android] Alarm Clock Pro 11.1.0一款经典简约个性的时钟
android·时钟
消失的旧时光-194310 小时前
ScheduledExecutorService
android·java·开发语言
小糖学代码10 小时前
MySQL:14.mysql connect
android·数据库·mysql·adb
怪兽201412 小时前
请谈谈什么是同步屏障?
android·面试
帅锅锅00713 小时前
SeLinux 全面详解
android·linux
只想搞钱的肥仔13 小时前
Android thermal (5)_cooling device(下)
android