Android Handler 机制完全解析:从源码到内存泄漏,一篇就够了

Android Handler 机制完全解析:从源码到内存泄漏,一篇就够了

本文将从 Looper → MessageQueue → Handler → 内存泄漏 这条主线出发,配合 AOSP 关键源码和注释,把 Handler 机制讲清楚。读完这篇,面试官再问 Handler,你可以从 ActivityThread 的第一行代码开始讲起。


一、为什么需要 Handler?

Android 的主线程(UI 线程)不允许执行耗时操作 (否则 ANR),同时非主线程不能直接更新 UI。Handler 就是连接"后台线程"和"主线程"的桥梁:子线程完成任务后,通过 Handler 把结果抛回主线程执行。

但这只是表象。Handler 的本质是 跨线程通信 + 任务调度 ------它依赖于 Looper + MessageQueue 构成的消息循环


二、架构总览:四大组件

组件 职责 数量
Looper 消息循环,不断从队列取消息 每个线程最多 1 个
MessageQueue 消息队列,按时间排序存储 Message 与 Looper 一一对应
Handler 发送 & 处理消息 可绑定同一 Looper 的多个
Message 消息载体,携带数据 + 处理目标 可复用(对象池)

一句话流程:

Handler.sendMessage() → MessageQueue.enqueueMessage() → Looper.loop() → MessageQueue.next() → Handler.dispatchMessage()


三、Looper:消息循环的发动机

3.1 谁创建了主线程的 Looper?

很多人知道"主线程默认有 Looper",但很少有人看过 ActivityThread 的源码:

java 复制代码
// android/app/ActivityThread.java

public static void main(String[] args) {
    // ...

    // ① 为主线程创建 Looper 和 MessageQueue
    Looper.prepareMainLooper();

    // ② 开始消息循环 ------ 主线程就在这里"卡住"了
    Looper.loop();

    // ③ loop() 返回意味着程序退出,正常情况下永远不会执行到这里
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

注意第 ③ 行------throw new RuntimeException。是的,如果 loop() 返回了,应用直接崩溃。因为主线程一旦退出消息循环,整个应用就废了。

3.2 Looper.prepare():ThreadLocal 的经典应用

prepareMainLooper() 最终调用的是 prepare(),而这里的核心就是 ThreadLocal

java 复制代码
// android/os/Looper.java

// ★ ThreadLocal:每个线程独立存储自己的 Looper 实例
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
    // 一个线程只能有一个 Looper ------ 重复 prepare 直接抛异常
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 创建 Looper(内部会创建 MessageQueue),塞进当前线程的 ThreadLocal
    sThreadLocal.set(new Looper(quitAllowed));
}

// 供其他线程调用
public static void prepare() {
    prepare(true); // 子线程允许退出
}

// 主线程专用
public static void prepareMainLooper() {
    prepare(false); // 主线程不允许退出 MessageQueue

    // ★ 将主线程的 Looper 额外保存一份到 sMainLooper
    //    供 Looper.getMainLooper() 静态方法随时获取(不依赖 ThreadLocal)
    //    这样其他线程要往主线程发消息时,不需要自己在主线程里取
    setMainLooper(myLooper());
}

// Looper 中的 sMainLooper 字段
private static Looper sMainLooper; // guarded by Looper.class

public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

// setMainLooper 的实现(包内可见)
// 其实就是把主线程的 Looper 赋值给 sMainLooper 静态变量
private static void setMainLooper(Looper looper) {
    synchronized (Looper.class) {
        sMainLooper = looper;
    }
}

关键理解:

  • ThreadLocal<Looper> 是一个线程局部变量,每个线程存取的都是自己的副本
  • 调用 Looper.prepare() 的线程,内部 new Looper() 会创建 MessageQueue,然后通过 sThreadLocal.set() 存入当前线程
  • 之后调用 Looper.myLooper() 就是从 sThreadLocal.get() 取出,这就是 Handler 绑定 Looper 的底层原理
java 复制代码
public static @Nullable Looper myLooper() {
    return sThreadLocal.get(); // 从当前线程取出之前存进去的 Looper
}

3.2.1 ThreadLocal 内部结构:一张图看懂

Looper.sThreadLocal.set(looper) 看起来只是一行代码,但底层结构远比想象中复杂。它的内部数据结构是这样的:

vbnet 复制代码
┌────────────────────────────────────────────────────────────┐
│                   Thread(线程对象)                         │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │ threadLocals (ThreadLocal.ThreadLocalMap)        │     │
│  │                                                  │     │
│  │  ┌────────────────────────────────────────────┐  │     │
│  │  │ Entry[] table                              │  │     │
│  │  │                                             │  │     │
│  │  │  ┌──────────────────┐  ┌──────────────────┐ │  │     │
│  │  │  │ Entry            │  │ Entry            │ │  │     │
│  │  │  │  key: ────────┐  │  │  key: ────────┐  │ │  │     │
│  │  │  │  [WeakRef]    │  │  │  [WeakRef]    │  │ │  │     │
│  │  │  │  ThreadLocal  │  │  │  ThreadLocal  │  │ │  │     │
│  │  │  │  <Looper>     │  │  │  <String>     │  │ │  │     │
│  │  │  │               │  │  │               │  │ │  │     │
│  │  │  │  value: ❤️    │  │  │  value: 🧵    │  │ │  │     │
│  │  │  │  Looper       │  │  │  "hello"      │  │ │  │     │
│  │  │  │  (mQueue,     │  │  │               │  │ │  │     │
│  │  │  │   mThread)    │  │  │               │  │ │  │     │
│  │  │  └───────────────┘  └──────────────────┘  │ │     │
│  │  └────────────────────────────────────────────┘  │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

         ▲  sThreadLocal.set(looper)          ▲  otherTl.set("hello")
         │                                     │
    Looper.sThreadLocal                  其他 ThreadLocal
    (static final)                       实例

关键要点:

  1. Thread 内部有一个 ThreadLocalMap 字段(名叫 threadLocals ,这是数据真正的存储容器。ThreadLocal 本身不存数据,它只是查找时的 key。

  2. ThreadLocalMap 内部维护一个 Entry[] 数组 ,采用开放地址法(不是拉链法)解决哈希冲突。

  3. 每个 EntrykeyThreadLocal 实例(弱引用),value 是存入的值

    • keyWeakReference 包装:static class Entry extends WeakReference<ThreadLocal<?>>
    • 这意味着:如果 ThreadLocal 对象自身不再被强引用(比如 sThreadLocal 被置 null),GC 时可以回收 Entry 的 key,但 value 仍存在强引用,这就是 ThreadLocal 内存泄漏的根因。
  4. 取值流程sThreadLocal.get() → 获取当前线程的 threadLocals → 以 sThreadLocal 为 key 在 Entry[] 中查找 → 返回对应的 value(Looper 实例)。

  5. 主线程的特殊性sThreadLocalstatic final,类加载后永远不会被 GC,所以主线程 Looper 在进程存活期间永远不会被回收。

java 复制代码
// Thread.java 内部的字段
ThreadLocal.ThreadLocalMap threadLocals = null;

// ThreadLocal.set() 的简化逻辑
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);  // t.threadLocals
    if (map != null) {
        map.set(this, value);        // this 就是 ThreadLocal 实例自身
    } else {
        createMap(t, value);
    }
}

// ThreadLocal.get() 的简化逻辑
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    return setInitialValue(); // 没找到就设初始值
}

总结一句: sThreadLocal.set(looper) 并不是往 ThreadLocal 对象里塞东西,而是往当前线程的 ThreadLocalMap 里插入了一条 Entry(key = sThreadLocal, value = looper)。取的时候从当前线程 的 map 里以 sThreadLocal 查。这就是**"线程隔离"的精髓**。

3.3 Looper 构造方法:创建 MessageQueue

java 复制代码
private Looper(boolean quitAllowed) {
    // Looper 内部持有一个 MessageQueue ------ 一对一关系
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

3.4 Looper.loop():永不终止的 for 循环

java 复制代码
// android/os/Looper.java

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        // 没有 prepare 就 loop ------ 直接崩
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // ★ 确保当前线程的 IPC 监听正常工作(和 Binder 有关)
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    // ★ 死循环 ------ 不断从 MessageQueue 取消息
    for (;;) {
        // ① 从队列取消息(可能阻塞)
        Message msg = queue.next(); // ★ 关键方法,下文详析
        if (msg == null) {
            // next() 返回 null 表示队列已退出,结束循环
            return;
        }

        // ② 分发消息给 Handler
        msg.target.dispatchMessage(msg); // ★ 关键分发,后文详析

        // ③ 回收 Message 回对象池
        msg.recycleUnchecked();
    }
}

面试高频题:为什么 Looper.loop() 不会导致 ANR?

答:loop()queue.next() 处阻塞。当队列没有消息时,next() 通过 nativePollOnce() 进入内核态的 epoll 休眠 ,不消耗 CPU。有新消息入队时,nativeWake() 唤醒。所以主线程一直在 loop 里"等着",但大部分时间是在休眠------直到有事件(点击、触摸、刷新)需要处理。


四、MessageQueue:消息的优先级队列

4.1 enqueueMessage():消息入队

当你调用 handler.sendMessage(msg),最终会走到这里:

java 复制代码
// android/os/MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
    // ● Handler 不能为空
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // ● 消息不能正在使用中
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            // 队列已退出,回收消息
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when; // ★ 这个消息应该"什么时候"被执行(毫秒时间戳)

        Message p = mMessages; // 链表头
        boolean needWake;

        if (p == null || when == 0 || when < p.when) {
            // ① 队列空,或者这条消息是"紧急"的(when=0 或比头还早)
            //    → 插到头部
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; // 如果正在休眠,需要唤醒
        } else {
            // ② 按时间顺序插入链表(升序)
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false; // 已有异步消息等着了,不需要额外唤醒
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // ★ 如果需要,唤醒正在 next() 中 nativePollOnce 阻塞的线程
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

关键点:

  • MessageQueue 内部是单向链表 ,按 when(执行时间戳)升序排列
  • 插入如果影响头节点,且队列正在阻塞等待,就调用 nativeWake() 唤醒 Looper 线程重新取消息
  • when == 0 表示立即执行,总是插到头部

4.2 next():消息出队

java 复制代码
// android/os/MessageQueue.java

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) return null; // 队列已销毁

    int pendingIdleHandlerCount = -1; // -1 表示还没初始化
    int nextPollTimeoutMillis = 0; // 阻塞时间

    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            // 调用 native 层的 epoll_wait,释放 CPU 等待
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis); // ★ 阻塞!

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            // ★★★ 同步屏障处理 ★★★
            if (msg != null && msg.target == null) {
                // 遇到屏障消息(target == null),跳过所有同步消息,
                // 找到第一个异步消息(isAsynchronous() == true)优先执行
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    // 消息时间还没到 → 计算还需等待多久
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // ★ 时间到了 → 取出这个消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next; // 从链表中移除
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg; // ← 返回给 loop() 分发
                }
            } else {
                // 队列空 → 一直等,直到被 nativeWake 唤醒
                nextPollTimeoutMillis = -1;
            }

            // ★★★ IdleHandler 处理 ★★★
            // 只有在队列空或当前消息还没到时间时,才执行 IdleHandler
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue; // 没有 IdleHandler → 继续阻塞等待
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // ★ 执行 IdleHandler(在 synchronized 块外执行,避免死锁)
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release reference

            boolean keep = false;
            try {
                keep = idler.queueIdle(); // ★ 执行空闲回调
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                // 返回 false → 执行一次后自动移除
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // 重置 IdleHandler 计数器,回到外层循环取消息
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0; // 立刻再检查一次是否有消息可用
    }
}

五、Handler:消息的发送与处理

5.1 创建 Handler:绑定哪个 Looper?

java 复制代码
// android/os/Handler.java

// ① 默认构造 ------ 绑定当前线程的 Looper
public Handler() {
    this(null, false);
}

// ② 指定 Looper
public Handler(Looper looper) {
    this(looper, null, false);
}

// ③ 最底层构造
public Handler(@Nullable Callback callback, boolean async) {
    // ★ 从 ThreadLocal 取当前线程的 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        // 当前线程没有调用 Looper.prepare() → 崩溃
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;  // 绑定 MessageQueue
    mCallback = callback;      // 回调接口
    mAsynchronous = async;    // 是否默认异步消息
}

5.2 发送消息:sendMessage → enqueueMessage

java 复制代码
// Handler.java --- 发送消息的完整调用链

// ① 立即发送
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

// ② 延迟发送
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) delayMillis = 0;
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// ③ 所有 sendXxx 最终都汇聚到这里
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        return false;
    }
    // ★ 把 Handler 自身设为 Message 的 target
    //   后续 dispatchMessage 时通过 msg.target 找回对应的 Handler
    return enqueueMessage(queue, msg, uptimeMillis);
}

// ④ 入队
private boolean enqueueMessage(@NonNull MessageQueue queue,
        @NonNull Message msg, long uptimeMillis) {
    msg.target = this; // ★ 关键:Message 持有 Handler 引用!
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis); // 交给 MessageQueue
}

关键理解: msg.target = this ------ Message 持有 Handler 的强引用 。这一行是 Handler 内存泄漏链上的重要一环,后面会详细分析。

5.3 分发消息:dispatchMessage

当 Looper 取出消息后,调用 msg.target.dispatchMessage(msg)

java 复制代码
// Handler.java

public void dispatchMessage(@NonNull Message msg) {
    // ① 优先:Message 自己的 callback(Handler.post(runnable) 方式)
    if (msg.callback != null) {
        handleCallback(msg);
        return;
    }
    // ② 其次:Handler 构造时传入的 Callback 接口
    if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
            return; // 返回 true → 不再执行 handleMessage
        }
    }
    // ③ 最后:子类重写的 handleMessage
    handleMessage(msg);
}

// 三种回调优先级:
// msg.callback > mCallback > handleMessage()
private static void handleCallback(Message message) {
    message.callback.run(); // Runnable 的 run()
}

5.4 post(Runnable) vs sendMessage(Message)

java 复制代码
// Handler.java

public final boolean post(@NonNull Runnable r) {
    // 内部还是 sendMessageDelayed,只不过把 Runnable 塞进 Message.callback
    return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain(); // 从对象池拿
    m.callback = r;               // ★ callback 就是 Runnable
    return m;
}

两者本质相同。post(Runnable) 只是语法糖,把 Runnable 封装成了 Message。


六、同步屏障(Sync Barrier):让异步消息插队

6.1 为什么需要同步屏障?

Android 的 UI 渲染依赖 VSync(垂直同步信号) 。当屏幕需要刷新时,如果消息队列前面排了十几条普通消息,渲染动作就必须排队------这会导致掉帧

同步屏障就是用来解决这个问题的:让异步的渲染消息绕过同步消息,插队执行。

6.2 屏障消息长什么样?怎么用?

java 复制代码
// MessageQueue.java

// ★ 插入屏障消息
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++; // 唯一标识
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        // ★★★ 屏障消息的 target == null ★★★
        // 正常消息的 target 指向 Handler,但屏障消息不指向任何 Handler
        // next() 通过判断 msg.target == null 来识别屏障
        // msg.target = null;  ← Message.obtain() 默认就是 null

        // 插到链表头部
        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) {
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }

        return token;
    }
}

6.3 移除屏障

java 复制代码
// MessageQueue.java

public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        // 找到 token 匹配的屏障消息
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) throw new IllegalStateException("...");

        // 移除屏障
        if (prev != null) {
            prev.next = p.next;
        } else {
            mMessages = p.next;
        }

        // 如果移除了头节点而队列被阻塞,需要唤醒
        final boolean needWake;
        if (prev == null && p.next != null) {
            needWake = mMessages.target == null; // 下一个消息是不是也是屏障
        } else {
            needWake = false;
        }
        p.recycleUnchecked();

        if (needWake) {
            nativeWake(mPtr);
        }
    }
}

6.4 屏障消息在 next() 中的处理逻辑

回顾 next() 中的关键代码片段:

java 复制代码
// ★ 遇到屏障(target == null),跳过所有同步消息
if (msg != null && msg.target == null) {
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
    // ↑ 一直遍历,直到找到第一个异步消息
}

流程总结:

ini 复制代码
消息队列: [屏障] → [普通消息1] → [普通消息2] → [异步消息★] → [普通消息3]

next() 发现头是屏障:
  → 跳过普通消息1、普通消息2
  → 取出异步消息★ 返回给 Looper 分发
  → 屏障继续留在队列中,直到 removeSyncBarrier() 移除

知识点: ViewRootImpl.scheduleTraversals() 内部就是通过屏障 + 异步消息来实现 VSync 同步渲染的。每次 UI 刷新,先 post 屏障,再发异步渲染消息。


七、IdleHandler:空闲时机的利用

7.1 什么是 IdleHandler?

当 MessageQueue 当前没有消息消息还没到执行时间时,会执行注册的 IdleHandler。

java 复制代码
// MessageQueue.java --- IdleHandler 接口
public static interface IdleHandler {
    /**
     * 当消息队列空闲时调用
     * @return true  → 保留,下次空闲继续调用
     *         false → 执行一次后自动移除
     */
    boolean queueIdle();
}

7.2 注册与移除

java 复制代码
// MessageQueue.java

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) throw new NullPointerException("handler == null");
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

7.3 典型使用场景

java 复制代码
// ① 延迟加载(等界面渲染完成后再加载非关键资源)
Looper.myQueue().addIdleHandler(() -> {
    // 界面已渲染完成,在这里加载图片缓存
    ImageLoader.getInstance().warmUp();
    return false; // 执行一次就够了
});

// ② 用 IdleHandler 替代 Handler.postDelayed 做非紧急任务
//    性能更好,因为不需要同时维护一个定时器

7.4 IdleHandler 的触发时机(回顾 next())

next() 方法中,只有在以下情况才会触发 IdleHandler:

java 复制代码
// 条件:队列空,或者第一个消息还没到时间
if (pendingIdleHandlerCount < 0
        && (mMessages == null || now < mMessages.when)) {
    pendingIdleHandlerCount = mIdleHandlers.size();
}

注意:

  • IdleHandler 不是在每次循环都执行,只在队列空当前无到期消息时执行
  • 如果队列一直有消息,IdleHandler 永远不会触发
  • 不要在 IdleHandler 里做耗时操作------它会延迟下一个消息的处理

八、消息复用:Message.obtain() 与对象池

java 复制代码
// Message.java

// ★ 对象池:单链表,最多 50 个
private static Message sPool;
private static int sPoolSize;
private static final int MAX_POOL_SIZE = 50;

// 从对象池取,避免 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(); // 池空才 new
}

// 回收回池
void recycleUnchecked() {
    // 清空所有字段
    flags = FLAG_IN_USE;
    what = 0; arg1 = 0; arg2 = 0;
    obj = null; replyTo = null; sendingUid = -1; when = 0;
    target = null; callback = null; data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

最佳实践: 始终用 Message.obtain()Handler.obtainMessage() 获取 Message,避免 new Message()。差一个对象通常没啥,但在 ListView/RecyclerView 滑动场景下可能瞬间创建数千个 Message。


九、内存泄漏分析与解决

9.1 经典泄漏场景

java 复制代码
public class MainActivity extends AppCompatActivity {
    // ★ 非静态内部类,隐式持有 Activity 引用
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            // 假设这里更新 UI
            textView.setText("...");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 发送一个延迟 10 分钟的消息
        mHandler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);
    }
}

问题: 如果用户在消息触发前退出 Activity,这条延时消息还在 MessageQueue 里。引用链如下:

csharp 复制代码
Activity(想要回收)
    ↑ 隐式持有(非静态内部类)
Handler
    ↑ 持有(msg.target = this)
Message
    ↑ 持有(链表引用)
MessageQueue
    ↑ 持有
Looper(主线程的 Looper 永远不会被回收,因为它在 ActivityThread.main() 里)
    ↑ 持有(sThreadLocal.set())
ThreadLocal(static 变量,类加载到卸载一直存在)

引用链完整路径:

markdown 复制代码
静态区:ThreadLocal.sThreadLocal
  → Looper(主线程)
    → MessageQueue
      → Message(链表节点)
        → Handler(msg.target)
          → Activity(非静态内部类持有外部的引用)

结论: Activity 无法被 GC 回收,内存泄漏。

9.2 修复方案

方案一:静态内部类 + 弱引用(推荐)
java 复制代码
public class MainActivity extends AppCompatActivity {
    // ★ 静态内部类
    private static class SafeHandler extends Handler {
        // ★ 弱引用持有 Activity
        private final WeakReference<MainActivity> mActivityRef;

        SafeHandler(MainActivity activity) {
            super(Looper.getMainLooper());
            mActivityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity activity = mActivityRef.get();
            if (activity == null) return; // Activity 已被回收

            // 安全更新 UI
            activity.textView.setText("...");
        }
    }

    private final SafeHandler mHandler = new SafeHandler(this);
}
方案二:Activity/Fragment 销毁时移除所有未处理消息
java 复制代码
@Override
protected void onDestroy() {
    // ★ 移除该 Handler 所有未处理的 Message 和 Runnable
    mHandler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

参数传 null 表示移除所有与 mHandler 关联的消息。

9.3 子线程 Handler 为什么不会泄漏?

java 复制代码
new Thread(() -> {
    Looper.prepare();
    Handler handler = new Handler();
    Looper.loop();
}).start();

这个 Handler 不会泄漏 Activity,因为如果 Activity 和这个子线程没有引用关系------但如果 Handler 是匿名内部类,情况一样危险。

更关键的是:子线程执行完任务后应该调用 Looper.quit() 退出循环,否则线程永远不会结束,一样泄漏线程资源。

9.4 更深层:主线程 Looper 为什么永不释放

java 复制代码
// ActivityThread.main()
Looper.prepareMainLooper();
Looper.loop(); // 死循环,永不返回

因为 sThreadLocalstatic final 的类变量,它持有的主线程 Looper 引用链是:

erlang 复制代码
类静态区 → sThreadLocal → ThreadLocalMap.Entry → Looper → MessageQueue → ...

只要类加载器活着(应用进程活着),主线程 Looper 这个引用链就不会断,所以任何通过 Message.target → Handler → 外部类 这条线挂上去的对象都永久可达

这就是 Handler 内存泄漏比一般泄漏更隐蔽、更致命的地方------泄漏的不是暂时的,而是永久存在的


十、Handler 机制全景图

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                     ActivityThread.main()                    │
│                                                              │
│  ① Looper.prepareMainLooper()                                │
│     └─ sThreadLocal.set(new Looper())  ← ThreadLocal 存储    │
│        └─ new MessageQueue()           ← 创建消息队列         │
│                                                              │
│  ② Looper.loop()                     ← 启动消息循环(永不返回) │
│     └─ for (;;) {                                           │
│          msg = queue.next();           ← 取消息(可能阻塞)      │
│               ├─ nativePollOnce()      ← epoll 休眠           │
│               ├─ 同步屏障检测           ← 跳过同步消息          │
│               ├─ IdleHandler 执行       ← 空闲时执行           │
│               └─ return msg                                  │
│                                                              │
│          msg.target.dispatchMessage(msg)  ← 分发              │
│               ├─ msg.callback.run()      ← post(Runnable)     │
│               ├─ mCallback.handleMessage() ← Callback 接口    │
│               └─ handleMessage()        ← 子类重写            │
│                                                              │
│          msg.recycleUnchecked()         ← 回收到对象池         │
│     }                                                        │
└─────────────────────────────────────────────────────────────┘

         子线程                             主线程
  ┌──────────────┐                 ┌──────────────────────┐
  │ Handler       │──sendMessage──→│ MessageQueue         │
  │ .sendMessage()│                │  [屏障] [Msg1] [★]   │
  │ .post()       │                └──────────┬───────────┘
  └──────────────┘                            │
                                              ▼
                                       Looper.loop()
                                              │
                                              ▼
                                       Handler.dispatchMessage()
                                              │
                                              ▼
                                         更新 UI

十一、面试常见追问

Q1:一个线程可以有几个 Handler?几个 Looper?

  • Looper:最多 1 个(prepare() 重复调用抛异常)
  • Handler:可以有多个,全部绑定同一个 Looper 及其 MessageQueue
  • 主线程不需要自己 prepare,ActivityThread 已经调好了

Q2:Handler 的 post 和 sendMessage 有什么区别?

没有本质区别。post(Runnable) 内部还是 sendMessageDelayed(),只是把 Runnable 设为 msg.callback,在 dispatchMessage() 时直接 callback.run()。优先级上 msg.callback 高于 handleMessage()

Q3:Looper.loop() 为什么不会卡死主线程?

因为 queue.next() 在无消息时通过 nativePollOnce(ptr, timeout) 进入 epoll 休眠,不消耗 CPU。有新消息入队时,nativeWake() 唤醒。这段休眠完全由 Linux 内核 epoll 机制管理,高效且节能。

有人说"那 MessageQueue 没消息不就一直卡在 next() 里?"------对,但 Android 的事件驱动模型就是这样。永远不返回,下次有新消息(点击、触摸、刷新)时唤醒就行。这就是"事件循环"的本质。

Q4:子线程 Looper 和主线程 Looper 有什么区别?

对比项 主线程 Looper 子线程 Looper
prepare prepareMainLooper() prepare()
能否 quit quitAllowed = false quitAllowed = true
创建者 ActivityThread.main() 开发者
生命周期 进程存活期 手动管理

Q5:同步屏障的原理能不能画一下?

scss 复制代码
同步消息排在前 → postSyncBarrier → 屏障消息插到头部
    [屏障] → [同步A] → [同步B] → [异步C] → [同步D]

next() 检测到头部 target==null(屏障)
    → 跳跳跳跳过同步A、同步B
    → 取出异步C

屏障一直留着,直到 removeSyncBarrier()

十二、总结

  • Looper 是驱动,通过 ThreadLocal 保证每个线程只有一个,由 ActivityThread.main() 为 UI 线程创建
  • MessageQueue 是容器,内部用单向链表按时间排序,通过 nativePollOnce/nativeWake(epoll)实现高效阻塞等待
  • Handler 是入口/出口,发消息时设 msg.target = this,取消息时通过 msg.target.dispatchMessage() 回调
  • 同步屏障 让异步渲染消息插队,保证 UI 不掉帧
  • IdleHandler 利用空闲时间执行非紧急任务,性能优于 postDelayed
  • 内存泄漏 的根本原因是 ThreadLocal 的静态生命周期 + 非静态内部类的隐式引用,解决方案是静态内部类 + 弱引用 + 及时移除消息

本文源码基于 Android 14(API 34),核心部分与早年版本基本一致。如有错漏,欢迎指正。

相关推荐
ShineWinsu3 小时前
对于Linux:传输层协议UDP原理的解析
linux·c++·面试·udp·协议·传输层·计算机系统
GitLqr4 小时前
StatefulWidget 里的隐形炸弹:为什么不要在 State 类中使用 context.mounted
flutter·面试·dart
梦想的颜色5 小时前
2026 AI Agent 工程师完整技术图谱|从面试题「什么是本体 Ontology」切入,附精选面试题库
面试·知识图谱·langgraph·aiagent·大模型面试·本体·2026 面试真题
不简说6 小时前
JS 代码技巧 vol.10 — 20 个 V8 引擎原理,解释"为什么这样写快"
前端·javascript·面试
A富得流油的咸鸭蛋7 小时前
安卓鸿蒙面试
android·面试·harmonyos
swipe7 小时前
13|(前端转全栈)支付成功不等于结束:回调、幂等、超时关单和状态竞争
前端·后端·面试
thesky1234567 小时前
智能体面试准备(六):Agent 记忆系统设计——短期、长期与向量记忆的架构与实现
人工智能·面试·agent·智能体·记忆系统
swipe8 小时前
12|(前端转全栈)点击提交订单后,后端如何用事务守住价格、库存和订单?
前端·后端·面试
剧号8 小时前
互联网大厂Java面试全解析:Java SE 11, Spring Boot及微服务实战问答
java·微服务·面试·kafka·kubernetes·springboot·分布式系统