Android Handler 机制源码分析

Android Handler 机制源码分析

一、整体结构

csharp 复制代码
Handler 机制
├── Handler      - 发送消息、处理消息
├── Message      - 消息载体
├── MessageQueue - 消息队列(单链表,按 when 排序)
├── Looper       - 循环取消息并分发
└── ThreadLocal  - 每个线程持有自己的 Looper

二、Looper.prepare():初始化 Looper

java 复制代码
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

步骤说明

步骤 逻辑 说明 原因
1 sThreadLocal.get() != null 检查当前线程是否已有 Looper 避免同一线程重复 prepare,导致多个 Looper 竞争同一 MessageQueue,引发消息错乱
2 抛异常 一个线程只能有一个 Looper 保证线程与 Looper 一一对应,消息路由清晰
3 new Looper(quitAllowed) 创建 Looper,内部会创建 MessageQueue Looper 需要持有自己的消息队列,用于存储待处理消息
4 sThreadLocal.set(looper) 将 Looper 存入当前线程的 ThreadLocal 线程隔离,每个线程只能访问自己的 Looper,实现多线程各自的消息循环

三、Looper 构造函数

java 复制代码
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);  // 创建消息队列
    mThread = Thread.currentThread();        // 绑定当前线程
}

步骤说明

步骤 逻辑 说明 原因
1 new MessageQueue(quitAllowed) 创建该 Looper 的消息队列 每个 Looper 需要独立队列存储消息,quitAllowed 控制主线程 Looper 是否允许退出
2 mThread = Thread.currentThread() 记录 Looper 所在线程 用于校验 Handler 是否在正确线程处理消息,以及 debug 时定位线程

四、Looper.loop():消息循环

java 复制代码
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    // 步骤1:获取当前线程的 Looper 和 MessageQueue
    
    for (;;) {
        Message msg = queue.next();  // 步骤2:阻塞取消息
        if (msg == null) {
            return;  // 队列退出时返回
        }
        // 步骤3:分发消息
        msg.target.dispatchMessage(msg);
        msg.recycleUnchecked();  // 步骤4:回收 Message 到池
    }
}

步骤说明

步骤 逻辑 说明 原因
1 myLooper()me.mQueue 获取当前线程的 Looper 和 MessageQueue 通过 ThreadLocal 取本线程 Looper,保证 loop 操作的是本线程的队列
2 queue.next() 阻塞取下一个消息,无消息时线程休眠 有消息时立即处理,无消息时休眠避免空转耗 CPU,由 enqueueMessage 的 nativeWake 唤醒
3 msg.target.dispatchMessage(msg) 调用 Handler 的 dispatchMessage 处理消息 target 即发送该消息的 Handler,保证消息由正确的 Handler 处理
4 msg.recycleUnchecked() 将 Message 回收到对象池 减少 GC,Message 使用频繁,复用可显著降低内存分配和回收开销

五、MessageQueue.next():取消息

java 复制代码
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) return null;  // native 层已销毁
    
    int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    
    for (;;) {
        // 步骤1:native 阻塞,nextPollTimeoutMillis 为 0 不阻塞,-1 永久阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prev = null;
            Message msg = mMessages;  // 链表头
            
            if (msg != null && msg.target == null) {
                // 步骤2:遇到屏障消息,跳过普通消息,只处理异步消息
                do {
                    prev = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            
            if (msg != null) {
                if (now < msg.when) {
                    // 步骤3:未到执行时间,计算等待时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 步骤4:到时间了,取出消息
                    mBlocked = false;
                    if (prev != null) prev.next = msg.next;
                    else mMessages = msg.next;
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;  // 无消息,永久阻塞
            }
            // ... IdleHandler 处理
        }
    }
}

步骤说明

步骤 逻辑 说明 原因
1 nativePollOnce(ptr, nextPollTimeoutMillis) 在 native 层阻塞,0 不阻塞,-1 永久阻塞 使用 epoll 等机制在 native 层休眠,比 Java 层 wait 更高效,且可与 native 事件(如输入、绘制)统一调度
2 msg.target == null 消息屏障,跳过普通消息,只处理异步消息 用于 View 绘制等需要优先执行的场景,屏障期间普通消息暂不处理,保证 UI 流畅
3 now < msg.when 未到执行时间,计算需要等待的毫秒数 支持延时消息,避免过早执行,nextPollTimeoutMillis 让 native 层精确唤醒
4 取出 msg、更新链表 从链表移除该消息并返回 消息已被消费,需从队列移除,防止重复处理
5 nextPollTimeoutMillis = -1 无消息时永久阻塞 队列空时线程休眠,等新消息入队时由 nativeWake 唤醒,避免忙等

六、Handler 发送消息:sendMessage / post

java 复制代码
// post(Runnable) 本质是发送一个 callback 类型的 Message
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;  // Runnable 存在 callback 字段
    return m;
}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) delayMillis = 0;
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) return false;
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;  // 绑定 Handler,用于 loop 时回调
    return queue.enqueueMessage(msg, uptimeMillis);
}

步骤说明

步骤 逻辑 说明 原因
1 post(Runnable) 封装成 Message,callback = r 统一用 Message 表示任务,Runnable 作为 callback 在 dispatchMessage 时执行
2 Message.obtain() 从对象池取 Message 避免频繁 new Message,降低 GC 压力
3 sendMessageDelayed 计算 uptimeMillis = now + delayMillis 将相对延时转为绝对时间戳,便于队列按时间排序
4 msg.target = this 指定处理该消息的 Handler loop 时通过 target 找到发送者,将消息分发给正确的 Handler
5 queue.enqueueMessage() when 插入 MessageQueue 保证延时消息和普通消息按时间顺序执行

七、MessageQueue.enqueueMessage():入队

java 复制代码
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) throw new IllegalArgumentException("Message must have a target.");
    if (msg.isInUse()) throw new IllegalStateException("msg already in use");
    
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        
        // 步骤1:队列空 或 新消息最早
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;  // 若正在阻塞,需要唤醒
        } else {
            // 步骤2:按 when 插入到合适位置
            needWake = false;
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) break;
            }
            msg.next = p;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);  // 步骤3:唤醒 native 层
        }
    }
    return true;
}

步骤说明

步骤 逻辑 说明 原因
1 `p == null when < p.when`
2 遍历链表 when 升序找到插入位置并插入 保证消息按时间顺序执行,延时消息在正确时机被取出
3 nativeWake(mPtr) 若在阻塞,唤醒 native 层 next() 在 nativePollOnce 中休眠,新消息入队后必须唤醒才能及时处理,否则会一直阻塞

八、Handler.dispatchMessage():分发消息

java 复制代码
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 步骤1:post(Runnable) 的情况,直接执行 callback
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // 步骤2:Handler 设置了 Callback,先交给它处理
            if (mCallback.handleMessage(msg)) return;
        }
        // 步骤3:最终交给 handleMessage
        handleMessage(msg);
    }
}

private static void handleCallback(Message msg) {
    msg.callback.run();  // 执行 Runnable
}

步骤说明

步骤 逻辑 说明 原因
1 msg.callback != null post(Runnable) 发送的,执行 msg.callback.run() post 时把 Runnable 存到 callback,此处直接执行,无需再建 Message 子类
2 mCallback != null 若 Handler 有 Callback,先由 Callback 处理 支持外部注入处理逻辑,若返回 true 表示已处理,不再调用 handleMessage
3 handleMessage(msg) 子类重写的方法,处理业务逻辑 作为默认处理入口,子类重写以实现具体业务

九、Message 对象池

java 复制代码
// Message 复用,避免频繁 new
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

void recycleUnchecked() {
    flags = FLAG_IN_USE;
    next = sPool;
    sPool = this;
    sPoolSize++;
}

步骤说明

步骤 逻辑 说明 原因
obtain 从 sPool 取头节点 有可用 Message 则复用 主线程消息量大,复用可减少 GC,提升性能
recycleUnchecked 将当前 Message 放回 sPool 头 用完后回收到池 与 obtain 配合形成复用链,避免频繁创建和销毁

十、整体流程图

scss 复制代码
主线程启动
    │
    ├─ Looper.prepare()  ──→ 创建 Looper、MessageQueue,存入 ThreadLocal
    │
    └─ Looper.loop()     ──→ 死循环
            │
            └─ queue.next()  ──→ 阻塞取消息
                    │
                    └─ msg.target.dispatchMessage(msg)  ──→ Handler 处理

Handler.post/sendMessage
    │
    ├─ Message.obtain()  ──→ 从池取 Message
    ├─ msg.target = this
    ├─ msg.callback = runnable (post 时)
    │
    └─ queue.enqueueMessage()  ──→ 按 when 插入队列
            │
            └─ nativeWake()  ──→ 若在阻塞则唤醒

十一、小结

组件 作用
Looper 绑定线程,持有 MessageQueue,loop() 循环取消息并分发
MessageQueue 单链表按 when 排序,native 层实现阻塞/唤醒
Handler 发送消息、处理消息,绑定到创建它的 Looper
Message 消息载体,含 target、callback、when,支持对象池复用
相关推荐
南城书生1 小时前
Android 大图加载与 OOM 优化
前端
南城书生2 小时前
RecyclerView 源码分析
前端
南城书生2 小时前
LeakCanary 原理分析
前端
没想好d2 小时前
通用管理后台组件库-13-页签组件
前端
xChive2 小时前
ECharts-大屏开发复习记录与踩坑总结
前端·javascript·echarts
南城书生2 小时前
Java HashMap 源码分析
前端
南城书生2 小时前
Java 线程池(ThreadPoolExecutor)源码分析
前端
前端Hardy2 小时前
别再靠 Code Review 纠格式了!一套自动化前端工程化方案,让 Vue 项目提交即合规
前端·程序员·代码规范
前端Hardy2 小时前
用 uni-app x 重构我们的 App:一套代码跑通 iOS、Android、鸿蒙!人力成本直降 60%
前端·ios·uni-app