一、引言
Handler 在安卓中的地位是不言而喻的,几乎维系着整个安卓程序运行的生命周期,但是这么重要的一个东西,我们真的了解它吗?下面跟随着我的脚步,慢慢揭开Hanler的神秘面纱吧!
本文将介绍Handler 的运行机制、MessageQueue、Looper 的关系,ThreadLocal,以及Handler 导致的内存泄漏问题
二、Handler 系统组成概览
在 Handler 的源码中,主要涉及以下核心组件:
- Message:封装消息的数据结构。
- MessageQueue:存储- Message的队列,内部是单链表。
- Looper:负责循环读取- MessageQueue并分发消息。
- Handler:对外提供- sendMessage()、- post()发送消息,并处理- MessageQueue中的消息。
它们之间关系如下图所示:

三、Handler 的创建
当 Handler 被创建时,它会绑定当前线程的 Looper:
            
            
              java
              
              
            
          
          public Handler() {
    this(Looper.myLooper(), null, false);
}
            
            
              java
              
              
            
          
          public Handler(Looper looper) {
    this(looper, null, false);
}最终调用:
            
            
              java
              
              
            
          
          public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
               boolean shared) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    mIsShared = shared;
}- mLooper通过- Looper.myLooper()获取当前线程的- Looper。
- mQueue由- Looper提供,确保所有- Handler在同一个- Looper线程内共享- MessageQueue。
重点 :主线程默认初始化
Looper,但子线程默认没有,需要手动Looper.prepare()。如果一定要在子线程中使用,推荐使用
HandlerThread,比于手动创建Looper,HandlerThread封装了Looper的创建和管理逻辑,代码更加简洁,也更易于维护。同时,HandlerThread有自己独立的消息队列,不会干扰主线程或其他线程的消息处理。
四、sendMessage() 如何发送消息
当我们调用 sendMessage() 时:
            
            
              java
              
              
            
          
          handler.sendMessage(msg);实际上调用:
            
            
              java
              
              
            
          
          public boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}最终:
            
            
              java
              
              
            
          
          public boolean sendMessageDelayed(Message msg, long delayMillis) {
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}最终调用 enqueueMessage():
            
            
              java
              
              
            
          
          private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; // 绑定 Handler
    return queue.enqueueMessage(msg, uptimeMillis);
}
            
            
              java
              
              
            
          
          @UnsupportedAppUsage
/*package*/ Handler target;也就是说 Message 引用了 Handler,这也为内存泄漏埋下伏笔
五、MessageQueue 插入消息
        
            
            
              java
              
              
            
          
          boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // 插入 MessageQueue
                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;
            }
    }
    return true;
}enqueueMessage 方法负责将消息按照时间顺序正确地插入到单链表结构的队列 中,按 when 进行排序。
六、Looper 如何处理消息
Looper.loop() 读取消息
            
            
              java
              
              
            
          
          public static void loop() {
    for (;;) {
        Message msg = queue.next(); // 取出消息
        //...
        msg.target.dispatchMessage(msg); // 交给 Handler 处理
    }
}MessageQueue.next()
            
            
              java
              
              
            
          
          Message next() {
    // 检查消息队列是否已销毁,若销毁则返回 null
    if (mPtr == 0) return null;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // 若有超时时间,刷新 Binder 待处理命令
        if (nextPollTimeoutMillis != 0) Binder.flushPendingCommands();
        // 阻塞线程,等待新消息或超时
        nativePollOnce(mPtr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message msg = mMessages;
            // 若为屏障消息,找下一个异步消息
            if (msg != null && msg.target == null) {
                do { 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 {
                    // 若消息到处理时间,从队列移除并返回
                    mMessages = msg.next;
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 若无消息,一直阻塞
                nextPollTimeoutMillis = -1;
            }
            // 若消息队列正在退出,释放资源并返回 null
            if (mQuitting) {
                dispose();
                return null;
            }
        }
    }
}nativePollOnce() 让当前线程进入阻塞状态,直到有新的消息到来或者超时
nativePollOnce() 的主要功能是:
- 线程阻塞:让当前线程进入等待状态,避免空转消耗CPU资源
- 事件唤醒:当有新消息到达或超时发生时,立即唤醒线程处理
- Native 层集成:与 Linux 的 epoll 机制对接,实现高效I/O多路复用
            
            
              java
              
              
            
          
          void nativePollOnce(long ptr, int timeoutMillis)ptr:指向 Native Looper 对象的指针(C++对象地址)
timeoutMillis 的含义:
- 如果 timeoutMillis > 0
- epoll_wait最多阻塞 timeoutMillis 毫秒,期间如果有事件发生,则提前返回。
 
- 如果 timeoutMillis == 0
- epoll_wait立即返回(非阻塞)。
 
- 如果 timeoutMillis < 0
- epoll_wait无限等待,直到有事件触发。
 
最终调用了 Linux epoll 机制 来监听消息事件。
七、nativePollOnce 方法调用流程
Java 层调用
            
            
              java
              
              
            
          
          // MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis);JNI 本地方法 ,由 MessageQueue 调用,用于等待消息。
在 MessageQueue.next() 方法中:
            
            
              java
              
              
            
          
          // MessageQueue.java
nativePollOnce(mPtr, nextPollTimeoutMillis);它的作用是:
- 如果 MessageQueue里有消息,立即返回。
- 如果没有消息 ,则阻塞,直到有新的消息到来或 timeoutMillis超时。
JNI 层调用
            
            
              cpp
              
              
            
          
          static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    MessageQueue* mq = reinterpret_cast<MessageQueue*>(ptr);
    mq->pollOnce(timeoutMillis);
}将 Java 传来的 mPtr 转换成 MessageQueue* 对象,并调用 pollOnce() 方法。
Native 层 pollOnce()
在 MessageQueue.cpp:
            
            
              cpp
              
              
            
          
          void MessageQueue::pollOnce(int timeoutMillis) {
    mLooper->pollOnce(timeoutMillis);
}调用了 Looper::pollOnce(),进入 消息轮询 逻辑。
Looper 的 pollOnce()
在 Looper.cpp:
            
            
              cpp
              
              
            
          
          int Looper::pollOnce(int timeoutMillis) {
    return pollInner(timeoutMillis);
}这里调用 pollInner(timeoutMillis),它的核心逻辑是 使用 epoll_wait() 监听事件。
epoll 监听消息事件
pollInner(timeoutMillis) 的核心逻辑:
            
            
              cpp
              
              
            
          
          int Looper::pollInner(int timeoutMillis) {
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    if (eventCount > 0) {
        for (int i = 0; i < eventCount; i++) {
            // 处理事件
        }
    }
}其中:
- mEpollFd是- epoll文件描述符,用于监听多个文件描述符(FD)。
- epoll_wait()会 阻塞当前线程 ,直到:- 有 新消息可读
- 有 文件描述符事件触发
- 超时 (timeoutMillis毫秒后自动返回)
 
到这里,我们清楚了 nativePollOnce 的主要作用是等待新消息到达消息队列。当调用这个方法时,如果当前消息队列中没有需要立即处理的消息,线程会被阻塞,从而释放 CPU 资源,直到有新消息到来或者发生其他唤醒条件。
那么 epoll_wait() 如何监听消息?
epoll_wait() 监听哪些事件?
MessageQueue 的 pipe(管道) :当 Handler 发送消息时,写入 pipe,触发 epoll 事件。
输入事件 :当用户触摸屏幕或按键时,触发 epoll 事件。
文件描述符(FileDescriptor) :例如 Binder 进程间通信(IPC)事件。
等等...
消息如何触发 epoll?
- Handler.sendMessage()会向- MessageQueue写入数据:
            
            
              bash
              
              
            
          
            write(mWakeEventFd, "W", 1);- 
epoll_wait()监听到pipe有数据,返回。
- 
Looper处理新消息,Java 层Handler开始执行handleMessage()。
epoll_wait阻塞等待wakeFd上的可读事件,当有数据写入wakeFd,epoll_wait返回,线程被唤醒,这里并不关心写入wakeFd的具体数据是什么,只关心可读事件的发生。
pipe 的作用
让 Handler.sendMessage() 触发 epoll 事件,立即唤醒 Looper。
至此,综上,我们可以知道 epoll_wait() 只负责等待事件,不会提前返回"第一条消息" ,它只会返回"有事件触发"的信号,具体执行哪个消息是 MessageQueue.next() 的逻辑,它会选择最早应该执行的消息 ,这就是 Handler 的阻塞唤醒的核心逻辑所在!
八、Handler 处理消息
            
            
              java
              
              
            
          
          public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        msg.callback.run();
    } else {
        handleMessage(msg);
    }
}最终执行:
            
            
              java
              
              
            
          
          @Override
public void handleMessage(Message msg) {
    // 需要用户实现
}九、核心组件之间的关系
Thread
  └── ThreadLocal<Looper>
        └── Looper
              └── MessageQueue
                    └── Message1 → Message2 → ...
                          ↑
                       Handler- Handler持有对- MessageQueue的引用(间接通过- Looper)因为Handler中的- MessageQueue是从- Looper中获取的;
            
            
              java
              
              
            
          
              public Handler(@Nullable Callback callback, boolean async) {
        //..
        mQueue = mLooper.mQueue;
        //..
    }- 每个线程通过 ThreadLocal 绑定自己的 Looper;
- Looper 管理其对应的 MessageQueue;
这样它们的关系就清晰了,每个线程只有一个Looper(是由ThreadLocal确保的),可以有多个Handler。
            
            
              java
              
              
            
          
          public final class Looper {
    // 线程本地存储,每个线程一个Looper实例
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    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));
    }
}关于ThreadLocal 的详细介绍可以看这篇文章:深入剖析Java中ThreadLocal原理
十、内存泄漏问题分析及解决方案
我们都知道判断内存泄漏的依据是:短生命周期对象是否被长生命周期对象引用!既然使用Handler不当会导致内存泄漏,那么我们只需要找到被引用的源头,然后去解决。
Handler 导致内存泄漏的完整引用流程
- 匿名内部类或非静态内部类的隐式引用:
众所周知,在Java中 匿名内部类或非静态内部类会持有外部类的引用,如下:
            
            
              java
              
              
            
          
          public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 处理消息
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendEmptyMessageDelayed(0, 10000);
    }
}这里的mHandler是一个非静态内部类。非静态内部类会隐式持有外部类(这里是MainActivity)的引用。这意味着mHandler对象中包含了对MainActivity实例的引用。
- MessageQueue 对 Message 的持有:
在上面示例中,我们发送了一个延迟的Message,尽管只传了一个0,但是其内部也会封装为Message,这时候Handler 会将 Message对象并将其发送到与之关联的MessageQueue中,MessageQueue会持有这个Message对象,直到该消息被处理。
- Message 对 Handler 的持有:
由上面第四小节的sendMessage()可知,在放入队列的时候,会将Handler 与 Message 关联:
            
            
              java
              
              
            
          
          private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; // 绑定 Handler
    return queue.enqueueMessage(msg, uptimeMillis);
}主要作用是,让Message知道是从哪个Handler发送的,并最终让那个Handler 的 handleMessage去处理。
            
            
              java
              
              
            
          
          public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    @UnsupportedAppUsage
    private static Looper sMainLooper;  // guarded by Looper.class
    //...
}我们都知道,在主线程中,主线程的Looper会一直运行下去(或者说 Looper被 静态 ThreadLocal<Looper> 所引用),不能被停止,而MessageQueue 又被Looper 所引用,这就产生了一条完整的引用链:ThreadLocal<Looper> - Looper - MessageQueue - Message - Handler - MainActivity
** 解决方案**
- 使用静态内部类 + WeakReference:
要解决内存泄漏,就是把引用链上任意一条引用断开,让GC不可达就行了,其实我们能操作的就只有 Handler - **MainActivity **这一条引用:
            
            
              java
              
              
            
          
          static class MyHandler extends Handler {
    private final WeakReference<MyActivity> ref;
    MyHandler(MyActivity activity) {
        ref = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        MyActivity activity = ref.get();
        if (activity != null) {
            // Safe to use activity
        }
    }
}- 在 Activity 的 onDestroy()中清除消息:
            
            
              java
              
              
            
          
          handler.removeCallbacksAndMessages(null);其实,只要消息不是延迟很久或者反复堆积,就不会在 MessageQueue 中长时间滞留,从而也就不会延长 Handler 或其持有对象的生命周期。
想想,在实际开发中,谁会在Activity中延迟发送一个很长时间的消息,所以我们不必为 Handler 导致内存泄漏,过度紧张,稍微留意一下就可以避免了 😃
十一、最后
Handler 是 Android 消息机制的基础组成部分。通过对 Handler、Looper、MessageQueue 之间关系的理解,我们可以更深入掌握 Android 的线程模型与 UI 更新流程。
由于本人能力有限,并没有对 Handler 进行过度深入全面了解,比如同步屏障等,如果文章内容解读有误,还望不吝赐教。